溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中如何高速Map存取

發(fā)布時間:2021-10-19 17:53:42 來源:億速云 閱讀:311 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關Java中如何高速Map存取,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

 高速Map存取

使用EnumMap來存取Key是Enum的,會有較快的速度,如下是一個網(wǎng)關返回對象Result的的狀態(tài)屬性,是一個枚舉類

    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;
      }
    }

考慮到定義微服務網(wǎng)關返回對象,應該盡量使用java自帶類型,以避免各種序列化,反序列化問題,網(wǎng)關不返回此枚舉值,而是返回msg字段,因此可以構(gòu)造一個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);
      }
    }

構(gòu)造EnumMap的時候,內(nèi)部實際上通過一個數(shù)組保存了所有的枚舉值,索引是枚舉的ordinal得到 當要根據(jù)Enum來操作EnumMap,只需要先調(diào)用ordinal,得到其索引,然后直接操作數(shù)組即可。操作一維數(shù)組有這最快的速度

有些場景下Key是int類型,這時候可以參考第二章的IntMap

有很多Key-Value使用場景,都可以轉(zhuǎn)化為根據(jù)索引對數(shù)組的存取,我們學過的C語言,操作的是變量名,但實際上還是根據(jù)指針獲取到內(nèi)存中的值,Beetl模板語言,對變量的訪問,也不像其他腳本語言那樣,通過變量名訪問Map獲取其值,而是在編譯期間就為這個變量分配好了索引值,所有變量都保存在一個一維數(shù)組里,這樣的存取,相比于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還是在解析腳本語言的時候給變量設置了數(shù)組所在索引,因此如上腳本在Beetl中翻譯如下

    Object[] vars = context.vars;
    vars[0] =1 ;
    vars[1] = vars[0]+2

這里為變量a,b 分別設置了在變量表中的索引是0和1;

曾優(yōu)化過一個電商的基礎組件,電商系統(tǒng)每天調(diào)用這個組件的次數(shù)高達10+萬億次,這個組件是用來統(tǒng)計方法調(diào)用的時長,收集一段時間后,定期發(fā)送到 分析系統(tǒng),用于查找和分析方法的性能,其中有一部分代碼是記錄以調(diào)用時長分類,記錄條用次數(shù),下面的代碼

    Watch watch = Watch.instance("orderByWx"); //初始化,從微信來的訂單
    //調(diào)用其他業(yè)務邏輯.....
    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;
      }

      /**
       * 返回方法調(diào)用消耗的毫秒
       * [@return](https://my.oschina.net/u/556800)
       */
        private long millisConsume(){
          return  TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-start);
        }

    }

Watch的key屬性記錄調(diào)用類型,如訂單調(diào)用,商品查詢信息等,可以為任意值,start屬性記錄了調(diào)用時候的時間點,millis會在調(diào)用endWatch后記錄調(diào)用消耗的毫秒數(shù)。

Profile類用來記錄監(jiān)控信息,并通過其他后臺線程發(fā)送到性能分析中心,例子做了一定簡化,只呈現(xiàn)保存部分

    public class Profile {
      //調(diào)用時長和調(diào)用次數(shù)
      static Map<Integer, AtomicInteger> countMap = new ConcurrentHashMap<>();
      /**
       * 對調(diào)用時間計數(shù)
       * [@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用于計數(shù),Key為Integer類型,表示調(diào)用時長,Value為AtomicInteger,用來計數(shù),每次調(diào)用,都會自增一個

Profile性能有一點優(yōu)化空間,如果從業(yè)務角度考慮,大部分需要監(jiān)控的方法或者代碼塊,執(zhí)行時間并不長,假設不超過32毫秒(這是一個假設值,根據(jù)系統(tǒng)運維統(tǒng)計分析后得出),因此,可以考慮用一個32長度的數(shù)組來存放32毫秒以內(nèi)的所有計數(shù),超過32毫秒的再沿用以前的方法

    static Map<Integer, AtomicInteger> countMap = new ConcurrentHashMap<>();
    static final int MAX  = 32;
    //保存消耗時間為32毫秒的調(diào)用次數(shù)
    static AtomicInteger[] counts = new AtomicInteger[MAX];
    static{
        for(int i=0;i<MAX;i++){
            counts[i] = new  AtomicInteger();
        }
    }

    /**
    * 對調(diào)用時間計數(shù)
    * [@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數(shù)組記錄32毫秒以內(nèi)調(diào)用計數(shù),因此當addWatch被調(diào)用的時候,先判斷millis是否小于32毫秒,如果是,直接用數(shù)組獲取計數(shù)器,然后自增。否則,沿用以前的邏輯

優(yōu)化后,通過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

性能優(yōu)化提高了40%,盡管看著不如本書其他例子性能提升那么明顯,但考慮這是一個基礎工具,性能提升會對所有系統(tǒng)都有幫助,實際上,作者優(yōu)化為跟此性能監(jiān)控工具后,對業(yè)務系統(tǒng)的有提升5%的性能提升,在擁有數(shù)十萬臺服務器的大型電商系統(tǒng),這個系統(tǒng)提升還是有意義的。

看完上述內(nèi)容,你們對Java中如何高速Map存取有進一步的了解嗎?如果還想了解更多知識或者相關內(nèi)容,請關注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI