今天就跟大家聊聊有關Java中如何高速Map存取,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
高速Map存取
使用EnumMap來存取Key是Enum的,會有較快的速度,如下是一個網關返回對象Result的的狀態屬性,是一個枚舉類
public static enum Status { SUCCESS(1,"成功"),FAIL(2,"處理失敗"),DEGRADE(98,"成功降級"),UNKOWN(99,"未知異常"); private int code; String msg; Status(int code,String msg){ this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
考慮到定義微服務網關返回對象,應該盡量使用java自帶類型,以避免各種序列化,反序列化問題,網關不返回此枚舉值,而是返回msg字段,因此可以構造一個EnumMap,Key為Status枚舉類型,Value為Status.msg 屬性
Map<Status,String> enumMap =null; private void initEnumMap(){ enumMap = new EnumMap<Status,String>(Status.class); for(Status status:Status.values()) { enumMap.put(status,status.msg); } }
構造EnumMap的時候,內部實際上通過一個數組保存了所有的枚舉值,索引是枚舉的ordinal得到 當要根據Enum來操作EnumMap,只需要先調用ordinal,得到其索引,然后直接操作數組即可。操作一維數組有這最快的速度
有些場景下Key是int類型,這時候可以參考第二章的IntMap
有很多Key-Value使用場景,都可以轉化為根據索引對數組的存取,我們學過的C語言,操作的是變量名,但實際上還是根據指針獲取到內存中的值,Beetl模板語言,對變量的訪問,也不像其他腳本語言那樣,通過變量名訪問Map獲取其值,而是在編譯期間就為這個變量分配好了索引值,所有變量都保存在一個一維數組里,這樣的存取,相比于Map存取,有十倍以上性能提高。
如下一段腳本語言
var a = 1; var b = 2+a;
有些語言引擎會翻譯成類似如下java代碼
context.put("a",1); context.put("b",context.get("a")+2);
這里context是一個Map。從Map里通過Key存取盡管很快,但是Beetl還是在解析腳本語言的時候給變量設置了數組所在索引,因此如上腳本在Beetl中翻譯如下
Object[] vars = context.vars; vars[0] =1 ; vars[1] = vars[0]+2
這里為變量a,b 分別設置了在變量表中的索引是0和1;
曾優化過一個電商的基礎組件,電商系統每天調用這個組件的次數高達10+萬億次,這個組件是用來統計方法調用的時長,收集一段時間后,定期發送到 分析系統,用于查找和分析方法的性能,其中有一部分代碼是記錄以調用時長分類,記錄條用次數,下面的代碼
Watch watch = Watch.instance("orderByWx"); //初始化,從微信來的訂單 //調用其他業務邏輯..... Profile.add(watch.endWatch());//記錄一次
Watch類定義如下
public class Watch { String key; long start; long millis =-1; private Watch(String key){ this.key = key; this.start = System.nanoTime(); } public static Watch instance(String key){ return new Watch(key); } public Watch endWatch(){ millis = millisConsume(); return this; } /** * 返回方法調用消耗的毫秒 * [@return](https://my.oschina.net/u/556800) */ private long millisConsume(){ return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-start); } }
Watch的key屬性記錄調用類型,如訂單調用,商品查詢信息等,可以為任意值,start屬性記錄了調用時候的時間點,millis會在調用endWatch后記錄調用消耗的毫秒數。
Profile類用來記錄監控信息,并通過其他后臺線程發送到性能分析中心,例子做了一定簡化,只呈現保存部分
public class Profile { //調用時長和調用次數 static Map<Integer, AtomicInteger> countMap = new ConcurrentHashMap<>(); /** * 對調用時間計數 * [@param](https://my.oschina.net/u/2303379) watch */ public static void addWatch(Watch watch){ int consumeTime = (int)watch.millis; AtomicInteger count = countMap.get(consumeTime); if(count==null){ count = new AtomicInteger(); AtomicInteger old = countMap.putIfAbsent(consumeTime,count); if(old!=null){ count = old; } } count.incrementAndGet(); } }
Profile會初始化一個ConcurrentHashMap用于計數,Key為Integer類型,表示調用時長,Value為AtomicInteger,用來計數,每次調用,都會自增一個
Profile性能有一點優化空間,如果從業務角度考慮,大部分需要監控的方法或者代碼塊,執行時間并不長,假設不超過32毫秒(這是一個假設值,根據系統運維統計分析后得出),因此,可以考慮用一個32長度的數組來存放32毫秒以內的所有計數,超過32毫秒的再沿用以前的方法
static Map<Integer, AtomicInteger> countMap = new ConcurrentHashMap<>(); static final int MAX = 32; //保存消耗時間為32毫秒的調用次數 static AtomicInteger[] counts = new AtomicInteger[MAX]; static{ for(int i=0;i<MAX;i++){ counts[i] = new AtomicInteger(); } } /** * 對調用時間計數 * [@param](https://my.oschina.net/u/2303379) watch */ public static void addWatch(Watch watch){ int consumeTime = (int)watch.millis; if(consumeTime<MAX){ counts[consumeTime].incrementAndGet(); return ; } AtomicInteger count = countMap.get(consumeTime); //原有的Profile.addWatch邏輯,在此忽略 }
新完善的代碼使用counts數組記錄32毫秒以內調用計數,因此當addWatch被調用的時候,先判斷millis是否小于32毫秒,如果是,直接用數組獲取計數器,然后自增。否則,沿用以前的邏輯
優化后,通過JMH測試(com.ibeetl.code.ch05.WatchTest),性能略有提升,如下輸出:
Benchmark Mode Samples Score Score error Units c.i.c.c.WatchTest.better thrpt 20 16447.171 2195.344 ops/ms c.i.c.c.WatchTest.general thrpt 20 11566.545 601.579 ops/ms
性能優化提高了40%,盡管看著不如本書其他例子性能提升那么明顯,但考慮這是一個基礎工具,性能提升會對所有系統都有幫助,實際上,作者優化為跟此性能監控工具后,對業務系統的有提升5%的性能提升,在擁有數十萬臺服務器的大型電商系統,這個系統提升還是有意義的。
看完上述內容,你們對Java中如何高速Map存取有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。