巧用結構化Map節約內存和GC

        Map是我們常用的數據結構,比如HashMapLinkedHashMap。在系統中,我們一般用Map來做隨機訪問索引或者單純的作爲數據對象存儲容器。作爲索引沒什麼好說的,Map的性能是很高的。但作爲數據存儲容器,Map確實不大稱職的。

        爲什麼這麼說呢?因爲相比於一個java bean,Map存儲數據對象具有極大的空間浪費和輕微的性能損耗。一方面,Map的table,以及entry linkList都會對空間大量浪費;另一方面,世界上不存在完美的hash函數,所以Map存取實際上是有少量性能損耗的。也許有的童鞋會問,我們能不能所有地方都用java bean替代Map。答案的否定的,一定的應用場景下,你無法爲所有實體建造一個專門的java bean,這個時候Map就是最佳選擇。

        於是我們推出新的一種專門針對數據對象存儲容器的結構化Map的概念,算是普通LinkedHashMap的優化版吧。

        何爲結構化?結構化即類似Class的屬性固定化。固定化有什麼好處顯而易見,空間大量的節約。同時它仍然是Map,所以具有一定的Map存取靈活性,可以根據實際場景字段構造Map。

        簡單的說,結構化Map是同構的Map見共享散列信息的Map。因爲我們的開發中經常會使用Map來對特定表、特定事物做內存中的抽象,而這些對象都有一個共同性:只要是一個實體,他們的key全部是相同的,這是value不同。利用這一特點,我們把一個類實體的key與value的映射抽象爲共享的key與value數據偏移量的映射,稱爲head,而value則使用Object[]密集排列。每個StructureMap中實際上只有一個head指針以及一個Object[]容納所有的value。因此,對個StructureMap來說,空間是無任何浪費的,和一個java bean一樣。雖然head是一個普通LinkedHashMap,存在空間浪費,好在一類實體僅有一個head,所以空間也不算浪費。

        我們再從性能上來分析。StructureMap與普通Map相比,每次存取首先從head取出value的偏移量,再把value從Object[]中存取出來,多了一次對數組的隨機訪問 。而數組的隨機訪問是O(1)操作,可以忽略不計了。事實上,StructureMap比Map在實際應用中性能反而高些。一方面,因爲StructureMap一個類型實體共享一個head,head是很少的,我們完全可以把head的loadFactor調到很低,讓衝突儘可能少,增加head的性能,也等於增加了整個StructureMap的性能;另一方面,StructureMap創建很少的對象,且不存在reMap這個過程,所以在創建的時候比普通Map快得多。

        咱們直接上數據,在內存中構建10W個包含20個字段Map,並對這10W個Map所有字段進行一次讀取。StructureMap的表現完勝HashMap,時間和空間複雜度都只有後者的三分之一。

        所謂有得必有失,StructureMap確確實實是損失了Map的部分靈活性。StructureMap第一次屬性put稱爲錄製結構,一旦submitStructure()後表明某實體的結構已經確定,StructureMap就和一個java bean一樣,無法增加或刪除屬性了。還好,咱們的很多場景下,Map都映射到一個固定的實體,不需要一直保持增刪屬性的能力,所以這個損失也無所謂了。

 

        下面示例代碼。提示一下:StructureMap實現了Map接口,get/put用法和LinkedHashMap完全一樣,不過其他某些方法的行爲和標準的Map有些許出入,請注意看註釋。

 

創建和插入

Map<String, Integer> vm = new StructureMap<String, Integer>("OBJECT1");

for (int j = 0; j < 20; j++) {

vm.put("input-key" + j, j);

}

//這步是提交結構信息,只有第一次有效

((StructureMap<String, Integer>) vm).submitStructure();

 

訪問

for (int j = 0; j < 20; j++) {

vm.get("input-key" + j);

}

 

//這個操作咱強烈不推薦,

//因爲StructureMap裏面不使用Entry,實現這個方法純粹是爲了兼容Map

//使用這個完全背離了StructureMap減少使用對象,節約內存的初衷

//可以使用keySet()來依次get(),甚至直接使用values()

for (Map.Entry<String, Integer> entry :vm.entrySet()) {

System.out.println("key=" + entry.getKey() + "," + "value=" + entry.getValue());

}

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章