枚舉緩存工具

此文章為筆記,為閱讀其他文章的感受、補充、記錄、練習、匯總,非原創,感謝每個知識分享者。

文章目錄

  • 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、枚舉中實例數越多,緩存模式的性能優勢越多;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36016.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36016.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36016.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

不需要用@Param注解與需要用@Param注解的情況

不需要用Param注解&#xff1a; 1.只有一個參數時&#xff0c;不需要用Param注解。此時在不使用Parma注解的情況下&#xff0c;sql語句中的參數占位符名稱直接使用任何名稱均可&#xff1b; 2.方法參數是引用數據類型的情況下&#xff0c;不需要用Param注解。 需要用Param注…

QT生成Word PDF文檔

需求&#xff1a;將軟件處理的結果保存為一個報告文檔&#xff0c;文檔中包含表格、圖片、文字&#xff0c;格式為word的.doc和.pdf。生成word是為了便于用戶編輯。 開發環境&#xff1a;qt4.8.4vs2010 在qt的官網上對于pdf的操作介紹如下&#xff1a;http://qt-project.org/…

華為認證 | H3C廠商證書,含金量有多高?

華為H3C認證是中國第一家建立國際規范的完整的網絡技術認證體系&#xff0c;它的作用是不言而喻的&#xff0c;工作上它能給你帶來技能加分。 那么H3C認證網絡工程師證書含金量怎么樣呢&#xff1f;下面我們就來了解一下吧。 01 H3C認證網絡工程師證書含金量 全面覆蓋H3C相關…

微服務Eureka注冊中心

目錄 一、Eureka的結構和作用 二、搭建eureka-server 三、服務注冊 四、服務發現 假如我們的服務提供者user-service部署了多個實例&#xff0c;如圖&#xff1a; 存在的問題&#xff1a; order-service在發起遠程調用的時候&#xff0c;該如何得知user-service實例的ip地址…

深度學習快速入門系列---損失函數

在深度學習中&#xff0c;損失函數的作用是量化預測值和真實值之間的差異&#xff0c;使得網絡模型可以朝著真實值的方向預測&#xff0c;損失函數通過衡量模型預測結果與真實標簽之間的差異&#xff0c;反映模型的性能。同時損失函數作為一個可優化的目標函數&#xff0c;通過…

10個微服務設計模式

微服務設計模式是一種指導微服務架構設計和開發的一系列原則和實踐。微服務設計模式的目的是為了解決微服務架構中遇到的一些常見的問題和挑戰&#xff0c;比如服務劃分、服務通信、服務治理、服務測試等。微服務設計模式可以幫助我們構建出高效、可靠、可擴展、可維護的微服務…

九耶丨閣瑞鈦倫特-井字棋html5代碼

你想了解關于井字棋&#xff08;Tic-Tac-Toe&#xff09;的HTML代碼嗎&#xff1f;以下是一個簡單的井子棋的HTML代碼示例&#xff1a; <!DOCTYPE html> <html> <head><title>Tic-Tac-Toe</title><style>.board {display: flex;flex-wrap…

使用AT命令操作Modem 3G/4G模塊

1. 引言 AT命令是一種通信協議&#xff0c;用于控制和配置各種設備&#xff0c;尤其在通信領域中具有重要性。它的名稱來源于"ATtention"&#xff08;注意&#xff09;&#xff0c;因為命令通常以"AT"開頭。AT命令最早被用于調制解調器&#xff0c;用于與…

redis學習筆記(三)

文章目錄 key操作&#xff08;1&#xff09;查找鍵&#xff08;2&#xff09;判斷鍵是否存在&#xff08;3&#xff09;查看鍵的的值的數據類型&#xff08;4&#xff09;刪除鍵以及鍵對應的值&#xff08;5&#xff09;查看鍵的有效期&#xff08;6&#xff09;設置key的有效期…

springboot整合rabbitmq

rabbitmq的七種模式 Hello word 客戶端引入依賴 <!--rabbitmq 依賴客戶端--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.8.0</version></dependency> 生產者 imp…

STM32 LoRa源碼解讀

目錄結構&#xff1a; SX1278 |-- include | |-- fifo.h | |-- lora.h | |-- platform.h | |-- radio.h | |-- spi.h | |-- sx1276.h | |-- sx1276Fsk.h | |-- sx1276FskMisc.h | |-- sx1276Hal.h | |-- sx1276LoRa.h | -- sx1276LoRaMisc.h – src |-- fifo.c |-- lora.c |-- …

【解析postman工具的使用---基礎篇】

postman前端請求詳解 主界面1.常見類型的接口請求1.1 查詢參數的接口請求1.1.1 什么是查詢參數?1.1.2 postman如何請求 1.2 ?表單類型的接口請求1.2.1 復習下http請求1.2.2? 什么是表單 1.3 上傳文件的表單請求1.4? json類型的接口請求 2. 響應接口數據分析2.1 postman的響…

紅帽RHCA考試內容解析

紅帽RHCA考試內容解析&#xff1a;最新的RHCA有3大方向體系&#xff0c;考試內容分別為&#xff1a; 一、Platform 平臺技術 RH442&#xff1a;性能調優 使用紅帽企業Linux和紅帽網絡提供的工具來學習Linux的性能調優和規劃的技巧及方法&#xff0c;學習系統架構&#xff0c;…

什么是DNS欺騙及如何進行DNS欺騙

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、什么是 DNS 欺騙&#xff1f;二、開始1.配置2.Ettercap啟動3.操作 總結 前言 我已經離開了一段時間&#xff0c;我現在回來了&#xff0c;我終于在做一個教…

【AI】p54-p58導航網絡、藍圖和AI樹實現AI隨機移動和跟隨移動、靠近玩家揮拳、AI跟隨樣條線移動思路

p54-p58導航網絡、藍圖和AI樹實現AI隨機移動和跟隨移動、靠近玩家揮拳、AI跟隨樣條線移動思路 p54導航網格p55藍圖實現AI隨機移動和跟隨移動AI Move To&#xff08;AI進行移動&#xff09;Get Random Pointln Navigable Radius&#xff08;獲取可導航半徑內的隨機點&#xff09…

Java基礎十 - 設計模式

一、單例 1. 創建 餓漢式 package basic;public class ESingleton {// 先私有化靜態實例private static ESingleton eSingleton new ESingleton();// 私有化構造方法&#xff0c;防止外部實例化private ESingleton(){};// 提供全局訪問方法public static ESingleton geteSi…

時序預測 | MATLAB實現基于LSTM長短期記憶神經網絡的時間序列預測-遞歸預測未來(多指標評價)

時序預測 | MATLAB實現基于LSTM長短期記憶神經網絡的時間序列預測-遞歸預測未來(多指標評價) 目錄 時序預測 | MATLAB實現基于LSTM長短期記憶神經網絡的時間序列預測-遞歸預測未來(多指標評價)預測結果基本介紹程序設計參考資料 預測結果 基本介紹 Matlab實現LSTM長短期記憶神經…

識別和應對內存抖動

關于作者&#xff1a;CSDN內容合伙人、技術專家&#xff0c; 從零開始做日活千萬級APP。 專注于分享各領域原創系列文章 &#xff0c;擅長java后端、移動開發、人工智能等&#xff0c;希望大家多多支持。 目錄 一、導讀二、概覽三、案例分析3.1 使用memory-profiler3.2 使用 cp…

磁粉制動器離合器收放卷應用介紹

張力控制系統的開環閉環應用介紹,請查看下面文章鏈接: PLC張力控制(開環閉環算法分析)_張力控制plc程序實例_RXXW_Dor的博客-CSDN博客里工業控制張力控制無處不在,也衍生出很多張力控制專用控制器,磁粉制動器等,本篇博客主要討論PLC的張力控制相關應用和算法,關于繞線…

什么是 fullgc

GC GC 全稱為garbage collection,中文含義為垃圾回收&#xff0c;在jvm中的含義為回收無用內存空間 Young space 中文名為年輕代或者新生代&#xff0c;為JVM 堆的一部分&#xff0c;由分代GC概念劃分而來&#xff0c;保存生命周期較短的對象 Tenured space 中文名為老年代…