溫馨提示×

溫馨提示×

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

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

Java中如何高速Map存取

發布時間:2021-10-19 17:53:42 來源:億速云 閱讀:314 作者:柒染 欄目:大數據

今天就跟大家聊聊有關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存取有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

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

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女