枚举缓存工具
此文章为笔记,为阅读其他文章的感受、补充、记录、练习、汇总,非原创,感谢每个知识分享者。
文章目录
- 1. 背景
- 2. 枚举缓存
- 3. 样例展示
- 4. 性能对比
- 5. 总结
本文通过几种样例展示如何高效优雅的使用java枚举消除冗余代码。
1. 背景
枚举在系统中的地位不言而喻,状态、类型、场景、标识等等,少则十几个多则上百个,相信以下这段代码很常见,而且类似的代码到处都是,目标:消除这类冗余代码。
/*** 根据枚举代码获取枚举* */public static OrderStatus getByCode(String code){for (OrderStatus v : values()) {if (v.getCode().equals(code)) {return v;}}return null;}/*** 根据枚举名称获取枚举* 当枚举内的实例数越多时性能越差*/public static OrderStatus getByName(String name){for (OrderStatus v : values()) {if (v.name().equals(name)) {return v;}}return null;}
2. 枚举缓存
- 减少代码冗余,代码简洁
- 去掉for循环,性能稳定高效
模块设计图
缓存结构
源码分析
源码展示
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 枚举缓存*/
public class EnumCache {/*** 以枚举任意值构建的缓存结构**/static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_VALUE = new ConcurrentHashMap<>();/*** 以枚举名称构建的缓存结构**/static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_NAME = new ConcurrentHashMap<>();/*** 枚举静态块加载标识缓存结构*/static final Map<Class<? extends Enum>, Boolean> LOADED = new ConcurrentHashMap<>();/*** 以枚举名称构建缓存,在枚举的静态块里面调用** @param clazz* @param es* @param <E>*/public static <E extends Enum> void registerByName(Class<E> clazz, E[] es) {Map<Object, Enum> map = new ConcurrentHashMap<>();for (E e : es) {map.put(e.name(), e);}CACHE_BY_NAME.put(clazz, map);}/*** 以枚举转换出的任意值构建缓存,在枚举的静态块里面调用** @param clazz* @param es* @param enumMapping* @param <E>*/public static <E extends Enum> void registerByValue(Class<E> clazz, E[] es, EnumMapping<E> enumMapping) {if (CACHE_BY_VALUE.containsKey(clazz)) {throw new RuntimeException(String.format("枚举%s已经构建过value缓存,不允许重复构建", clazz.getSimpleName()));}Map<Object, Enum> map = new ConcurrentHashMap<>();for (E e : es) {Object value = enumMapping.value(e);if (map.containsKey(value)) {throw new RuntimeException(String.format("枚举%s存在相同的值%s映射同一个枚举%s.%s", clazz.getSimpleName(), value, clazz.getSimpleName(), e));}map.put(value, e);}CACHE_BY_VALUE.put(clazz, map);}/*** 从以枚举名称构建的缓存中通过枚举名获取枚举** @param clazz* @param name* @param defaultEnum* @param <E>* @return*/public static <E extends Enum> E findByName(Class<E> clazz, String name, E defaultEnum) {return find(clazz, name, CACHE_BY_NAME, defaultEnum);}/*** 从以枚举转换值构建的缓存中通过枚举转换值获取枚举** @param clazz* @param value* @param defaultEnum* @param <E>* @return*/public static <E extends Enum> E findByValue(Class<E> clazz, Object value, E defaultEnum) {return find(clazz, value, CACHE_BY_VALUE, defaultEnum);}private static <E extends Enum> E find(Class<E> clazz, Object obj, Map<Class<? extends Enum>, Map<Object, Enum>> cache, E defaultEnum) {Map<Object, Enum> map = null;if ((map = cache.get(clazz)) == null) {executeEnumStatic(clazz);// 触发枚举静态块执行map = cache.get(clazz);// 执行枚举静态块后重新获取缓存}if (map == null) {String msg = null;if (cache == CACHE_BY_NAME) {msg = String.format("枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByName(%s.class, %s.values());",clazz.getSimpleName(),clazz.getSimpleName(),clazz.getSimpleName(),clazz.getSimpleName());}if (cache == CACHE_BY_VALUE) {msg = String.format("枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByValue(%s.class, %s.values(), %s::getXxx);",clazz.getSimpleName(),clazz.getSimpleName(),clazz.getSimpleName(),clazz.getSimpleName(),clazz.getSimpleName());}throw new RuntimeException(msg);}if(obj == null){return defaultEnum;}Enum result = map.get(obj);return result == null ? defaultEnum : (E) result;}private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {if (!LOADED.containsKey(clazz)) {synchronized (clazz) {if (!LOADED.containsKey(clazz)) {try {// 目的是让枚举类的static块运行,static块没有执行完是会阻塞在此的Class.forName(clazz.getName());LOADED.put(clazz, true);} catch (Exception e) {throw new RuntimeException(e);}}}}}/*** 枚举缓存映射器函数式接口*/@FunctionalInterfacepublic interface EnumMapping<E extends Enum> {/*** 自定义映射器** @param e 枚举* @return 映射关系,最终体现到缓存中*/Object value(E e);}}
关键解读
开闭原则
什么是开闭原则?
对修改是封闭的,对新增扩展是开放的。为了满足开闭原则,这里设计成有枚举主动注册到缓存,而不是有缓存主动加载枚举,这样设计的好处就是:当增加一个枚举时只需要在当前枚举的静态块中自主注册即可,不需要修改其他的代码
比如我们现在要新增一个状态类枚举:
public enum StatusEnum {INIT("I", "初始化"),PROCESSING("P", "处理中"),SUCCESS("S", "成功"),FAIL("F", "失败");private String code;private String desc;StatusEnum(String code, String desc) {this.code = code;this.desc = desc;}public String getCode() {return code;}public String getDesc() {return desc;}static {// 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举EnumCache.registerByName(StatusEnum.class, StatusEnum.values());// 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);}
}
注册时机
将注册放在静态块中,那么静态块什么时候执行呢?
1、当第一次创建某个类的新实例时
2、当第一次调用某个类的任意静态方法时
3、当第一次使用某个类或接口的任意非final静态字段时
4、当第一次Class.forName时
如果我们入StatusEnum创建枚举,那么在应用系统启动的过程中StatusEnum的静态块可能从未执行过,则枚举缓存注册失败,
所有我们需要考虑延迟注册,代码如下:
private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {if (!LOADED.containsKey(clazz)) {synchronized (clazz) {if (!LOADED.containsKey(clazz)) {try {// 目的是让枚举类的static块运行,static块没有执行完是会阻塞在此的Class.forName(clazz.getName());LOADED.put(clazz, true);} catch (Exception e) {throw new RuntimeException(e);}}}}}
Class.forName(clazz.getName())被执行的两个必备条件:
1、缓存中没有枚举class的键,也就是说没有执行过枚举向缓存注册的调用,见EnumCache.find方法对executeEnumStatic方法的调用;
2、executeEnumStatic中的LOADED.put(clazz, true);还没有被执行过,也就是Class.forName(clazz.getName());没有被执行过;
我们看到executeEnumStatic中用到了双重检查锁,所以分析一下正常情况下代码执行情况和性能:
1、当静态块还未执行时,大量的并发执行find查询。
此时executeEnumStatic中synchronized会阻塞其他线程;第一个拿到锁的线程会执行Class.forName(clazz.getName());同时触发枚举静态块的同步执行;之后其他线程会逐一拿到锁,第二次检查会不成立跳出executeEnumStatic;2、当静态块已经执行,且静态块里面正常执行了缓存注册,大量的并发执行find查询。
executeEnumStatic方法不会调用,没有synchronized引发的排队问题;3、当静态块已经执行,但是静态块里面没有调用缓存注册,大量的并发执行find查询。
find方法会调用executeEnumStatic方法,但是executeEnumStatic的第一次检查通不过;
find方法会提示异常需要在静态块中添加注册缓存的代码;总结:第一种场景下会有短暂的串行,但是这种内存计算短暂串行相比应用系统的业务逻辑执行是微不足道的,
也就是说这种短暂的串行不会成为系统的性能瓶颈
3. 样例展示
构造枚举
public enum StatusEnum {INIT("I", "初始化"),PROCESSING("P", "处理中"),SUCCESS("S", "成功"),FAIL("F", "失败");private String code;private String desc;StatusEnum(String code, String desc) {this.code = code;this.desc = desc;}public String getCode() {return code;}public String getDesc() {return desc;}static {// 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举EnumCache.registerByName(StatusEnum.class, StatusEnum.values());// 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);}
}
测试类
public class Test{public static void main(String [] args){System.out.println(EnumCache.findByName(StatusEnum.class, "SUCCESS", null));// 返回默认值StatusEnum.INITSystem.out.println(EnumCache.findByName(StatusEnum.class, null, StatusEnum.INIT));// 返回默认值StatusEnum.INITSystem.out.println(EnumCache.findByName(StatusEnum.class, "ERROR", StatusEnum.INIT));System.out.println(EnumCache.findByValue(StatusEnum.class, "S", null));// 返回默认值StatusEnum.INITSystem.out.println(EnumCache.findByValue(StatusEnum.class, null, StatusEnum.INIT));// 返回默认值StatusEnum.INITSystem.out.println(EnumCache.findByValue(StatusEnum.class, "ERROR", StatusEnum.INIT));}
}
执行结果
SUCCESS
INIT
INIT
SUCCESS
INIT
INIT
4. 性能对比
对比代码,如果OrderType中的实例数越多性能差异会越大
public class Test {enum OrderType {_00("00", "00"),_01("01", "01"),_02("02", "02"),_03("03", "03"),_04("04", "04"),_05("05", "05"),_06("06", "06"),_07("07", "07"),_08("08", "08"),_09("09", "09"),_10("10", "10");private String code;private String desc;OrderType(String code, String desc) {this.code = code;this.desc = desc;}public String getCode() {return code;}public String getDesc() {return desc;}static {EnumCache.registerByValue(OrderType.class, OrderType.values(), OrderType::getCode);}public static OrderType getEnumByCode(String code, OrderType def) {OrderType[] values = OrderType.values();for (OrderType value : values) {if (value.getCode().equals(code)) {return value;}}return def;}}private static final OrderType DEF = OrderType._00;private static final int TIMES = 10000000;static void compare(String code) {long s = System.currentTimeMillis();for (int idx = 0; idx < TIMES; idx++) {OrderType.getEnumByCode(code, DEF);}long t = System.currentTimeMillis() - s;System.out.println(String.format("枚举->%s : %s", code, t));s = System.currentTimeMillis();for (int idx = 0; idx < TIMES; idx++) {EnumCache.findByValue(OrderType.class, code, DEF);}t = System.currentTimeMillis() - s;System.out.println(String.format("缓存->%s : %s", code, t));System.out.println();}public static void main(String[] args) throws Exception {for (int idx = 0; idx < 2; idx++) {compare("NotExist");for (OrderType value : OrderType.values()) {compare(value.getCode());}System.out.println("=================");}}
}
执行结果
枚举->NotExist : 312
缓存->NotExist : 105枚举->00 : 199
缓存->00 : 164枚举->01 : 313
缓存->01 : 106枚举->02 : 227
缓存->02 : 90枚举->03 : 375
缓存->03 : 92枚举->04 : 260
缓存->04 : 92枚举->05 : 272
缓存->05 : 78枚举->06 : 284
缓存->06 : 78枚举->07 : 315
缓存->07 : 76枚举->08 : 351
缓存->08 : 78枚举->09 : 372
缓存->09 : 81枚举->10 : 402
缓存->10 : 78=================
枚举->NotExist : 199
缓存->NotExist : 68枚举->00 : 99
缓存->00 : 91枚举->01 : 141
缓存->01 : 79枚举->02 : 178
缓存->02 : 77枚举->03 : 202
缓存->03 : 77枚举->04 : 218
缓存->04 : 81枚举->05 : 259
缓存->05 : 90枚举->06 : 322
缓存->06 : 78枚举->07 : 318
缓存->07 : 78枚举->08 : 347
缓存->08 : 77枚举->09 : 373
缓存->09 : 79枚举->10 : 404
缓存->10 : 78=================
5. 总结
1、代码简洁;
2、枚举中实例数越多,缓存模式的性能优势越多;
相关文章:

枚举缓存工具
此文章为笔记,为阅读其他文章的感受、补充、记录、练习、汇总,非原创,感谢每个知识分享者。 文章目录 1. 背景2. 枚举缓存3. 样例展示4. 性能对比5. 总结 本文通过几种样例展示如何高效优雅的使用java枚举消除冗余代码。 1. 背景 枚举在系统…...
【BASH】回顾与知识点梳理(二十五)
【BASH】回顾与知识点梳理 二十五 二十五. 特殊shell、PAM 模块、讯息传递和大量建置账号25.1 特殊shell特殊的 shell, /sbin/nologin 25.2 PAM模块25.3 Linux 主机上的用户讯息传递查询使用者: w, who, last, lastlog使用者对谈: write, mesg, wall使用…...

什么是Node js?什么是React?有什么区别
JavaScript是当今最流行的编程语言之一,它用于开发多种技术,两种这样的技术是Node.js和React。许多学生很难理解Nodejs和React之间的区别。 React和Nodejs之间的主要区别在于它们的使用位置。Nodejs 用于开发应用程序的服务器端,而Reactjs用于…...

使用postman做接口测试
1.接口测试:针对软件对外提供服务的接口的输入输出进行测试,以及接口间相互逻辑的测试,验证接口功能与接口描述文档的一致性 2.接口测试流程: 1)获取接口信息:通过接口文档或抓包来获取接口的基本调用方式和…...

VMware Workstation 如何启用复制粘贴
产品:VMware Workstation 16 Pro 版本:16.1.1 build-17801498 我们刚安装好的 VMware Workstation 会发现无法复制粘贴文件到虚拟机中,如下为解决方案: 1.点击 虚拟机,点击 安装 VMware Tools(T)...。 2.虚拟机下面会…...

免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务 bbc
1. 涉及平台 平台管理、商家端(PC端、手机端)、买家平台(H5/公众号、小程序、APP端(IOS/Android)、微服务平台(业务服务) 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端…...

VSCode-Python传参数进行Debug
新建demo.py import argparse def parse_args():description "debug example" parser argparse.ArgumentParser(descriptiondescription) help "The path of address"parser.add_argument(--host,help help) parser.add_ar…...

实践-传统深度学习
简介与安装 2 训练自己的数据集整体流程3 数据加载与预处理4 搭建网络模型5 学习率对结果的影响6 Drop-out操作7 权重初始化方法对比8 初始化标准差对结果的影响9 正则化对结果的影响10 加载模型进行测试 TensorFlow:每一步都需要自己做。 Keras:做起来更…...
爬虫:使用Selenium模拟人工操作及获取网页内容
专栏介绍 结合自身经验和内部资料总结的Python教程,每天3-5章,最短1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来! 全部文章请访问专栏:《Python全栈教程(0基础)》 再推荐一下最近热更的:《大厂测试高频面试题详解》 该专栏对…...

AOP开发
目录 1、简介 1.1、AOP 相关概念 1.2、AOP 开发明确的事项 1.3、知识要点 2、两种方式 3、基于 XML 3.1、快速入门 3.1.1、导入坐标 3.1.2、创建接口和实现类 3.1.3、创建切面 3.1.4、配置bean 3.1.5、配置织入 3.1.6、测试 3.2、切点表达式 3.2.1、表达式举例 …...

Streamlit项目: 轻松搭建部署个人博客网站
文章目录 1 前言1.1 探索 Streamlit:轻松创建交互式应用1.2 最全 Streamlit 教程专栏 2 我的个人博客网站已上线!2.1 一个集成了智能中医舌诊-中e诊专栏的博客网站2.2 前期准备2.3 使用 Streamlit Cloud 运行 3 知识点讲解3.1 实现多页面:两种…...

手把手教你如何实现内网搭建电影网站并进行公网访问(保姆级教学)
手把手教你如何实现内网搭建电影网站并进行公网访问 文章目录 手把手教你如何实现内网搭建电影网站并进行公网访问前言1. 把软件分别安装到本地电脑上1.1 打开PHPStudy软件,安装一系列电影网站所需的支持软件1.2 设置MacCNS10的运行环境1.3 进入电影网页的安装程序1…...

Redis_事务操作
13. redis事务操作 13.1事务简介 原子性(Atomicity) 一致性(Consistency) 隔离性(isolation) 持久性(durabiliby) ACID 13.2 Redis事务 提供了multi、exec命令来完成 第一步,客户端使用multi命令显式地开启事务第二步,客户端把事务中要执行的指令发…...

python质检工具(pylint)安装使用总结
1、Pylint Pylint工具主要类似java中的checkStyle和findbugs,是检查代码样式和逻辑规范的工具。 1.1、Pylint安装流程: 打开PyCharm软件,打开如图1.1所示Terminal终端窗口,先查看python版本和pip版本,pip是19.0.3,python是2.7 图1.1 运行pip install pylint安装pylin…...
“深入探究JVM:解密Java虚拟机的工作原理“
标题:深入探究JVM:解密Java虚拟机的工作原理 摘要:本文将深入探究Java虚拟机(JVM)的工作原理,包括JVM的组成部分、类加载过程、内存管理、垃圾回收机制以及即时编译器等。通过了解JVM的工作原理࿰…...
同济子豪兄模板 半天搞定图像分类
同济子豪兄模板 半天搞定图像分类 ‘’import cv2 import numpy as np import time from tqdm import tqdm 视频逐帧处理代码模板 不需修改任何代码,只需定义process_frame函数即可 def generate_video(input_path‘videos/robot.mp4’): filehead input_path.…...

接口自动化测试,Fiddler使用抓包辅助实战,一篇彻底打通...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、快捷设置&…...

概念解析 | 隐式神经表示:揭开神经网络黑盒的奥秘
注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:隐式神经表示(Implicit Neural Representations) 隐式神经表示:揭开神经网络黑盒的奥秘 近年来,神经网络在各种任务上取得了惊人的进步,但其内部表示方式依然难以解读,被称为“…...
深入浅出PHP封装根据商品ID获取淘宝商品详情数据方法
要通过淘宝的API获取商品详情,您可以使用淘宝开放平台提供的接口来实现。以下是一种使用PHP编程语言实现的示例,展示如何通过淘宝开放平台API获取商品详情: 首先,确保您已注册成为淘宝开放平台的开发者,并创建一个应用…...

自动切换HTTP爬虫ip助力Python数据采集
在Python数据采集中,如果你需要爬取一些网站的数据,并且需要切换IP地址来避免被封或限制,我们可以考虑以下几种方式来实现自动切换HTTP爬虫IP。 1. 使用代理服务器 使用代理服务器是常见的IP切换技术之一。你可以购买或使用免费的代理服务器…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...