1. 程式人生 > >【轉】HashMap,ArrayMap,SparseArray源碼分析及性能對比

【轉】HashMap,ArrayMap,SparseArray源碼分析及性能對比

sea 實現原理 ati 順序 dex lan time 提示 內存占用

HashMap,ArrayMap,SparseArray源碼分析及性能對比

技術分享圖片 jjlanbupt 2016.06.03 20:19* 字數 2165 閱讀 7967評論 13

ArrayMap及SparseArray是android的系統API,是專門為移動設備而定制的。用於在一定情況下取代HashMap而達到節省內存的目的。

一.源碼分析(由於篇幅限制,源碼分析部分會放在單獨的文章中)
二.實現原理及數據結構對比
三.性能測試對比
四.總結

一.源碼分析
稍後會在下一篇文章中補充(都寫在一篇,篇幅太長了)

二.實現原理及數據結構對比
1. hashMap

技術分享圖片
Paste_Image.png

從hashMap的結構中可以看出,首先對key值求hash,根據hash結果確定在table數組中的位置,當出現哈希沖突時采用開放鏈地址法進行處理。Map.Entity的數據結構如下:

static class HashMapEntry<K, V> implements Entry<K, V> {    
final K key;    
V value; 
final int hash;   
 HashMapEntry<K, V> next;
}   

具體的hashmap源碼細節會在其他文章中進行分析,這裏可以看出來的是,從空間的角度分析,HashMap中會有一個利用率不超過負載因子(默認為0.75)的table數組,其次,對於HashMap的每一條數據都會用一個HashMapEntry進行記錄,除了記錄key,value外,還會記錄下hash值,及下一個entity的指針。

時間效率方面,利用hash算法,插入和查找等操作都很快,且一般情況下,每一個數組值後面不會存在很長的鏈表(因為出現hash沖突畢竟占比較小的比例),所以不考慮空間利用率的話,HashMap的效率非常高。

2.ArrayMap

技術分享圖片 Paste_Image.png

ArrayMap利用兩個數組,mHashes用來保存每一個key的hash值,mArrray大小為mHashes的2倍,依次保存key和value。源碼的細節方面會在下一篇文章中說明。現在我們先拋開細節部分,只看關鍵語句:

mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;

相信看到這大家都明白了原理了。但是它怎麽查詢呢?答案是二分查找。當插入時,根據key的hashcode()方法得到hash值,計算出在mArrays的index位置,然後利用二分查找找到對應的位置進行插入,當出現哈希沖突時,會在index的相鄰位置插入。
總結一下,空間角度考慮,ArrayMap每存儲一條信息,需要保存一個hash值,一個key值,一個value值。對比下HashMap 粗略的看,只是減少了一個指向下一個entity的指針。還有就是節省了一部分可見空間上的內存節省也不是特別明顯。是不是這樣呢?後面會驗證。
時間效率上看,插入和查找的時候因為都用的二分法,查找的時候應該是沒有hash查找快,插入的時候呢,如果順序插入的話效率肯定高,但如果是隨機插入,肯定會涉及到大量的數組搬移,數據量大,肯定不行,再想一下,如果是不湊巧,每次插入的hash值都比上一次的小,那就得次次搬移,效率一下就扛不住了的感腳。

3.SparseArray

技術分享圖片 Paste_Image.png

sparseArray相對來說就簡單的多了,但是不要以為它可以取代前兩種,sparseArray只能在key為int的時候才能使用,註意是int而不是Integer,這也是sparseArray效率提升的一個點,去掉了裝箱的操作!。
因為key為int也就不需要什麽hash值了,只要int值相等,那就是同一個對象,簡單粗暴。插入和查找也是基於二分法,所以原理和Arraymap基本一致,這裏就不多說了。
總結一下:空間上對比,與HashMap,去掉了Hash值的存儲空間,沒有next的指針占用,還有其他一些小的內存占用,看著節省了不少。
時間上對比:插入和查找的情形和Arraymap基本一致,可能存在大量的數組搬移。但是它避免了裝箱的環節,不要小看裝箱過程,還是很費時的。所以從源碼上來看,效率誰快,就看數據量大小了。

好啦,說半天都是分析,下面來點實際的,用數據說話!

三.性能測試對比
我們從插入和查詢兩方面來比對試試看。

1.插入性能時間對比
測試代碼:

long start = System.currentTimeMillis();
Map<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) { 
   hash.put(i, i+"");
}
long ts = System.currentTimeMillis() - start;

就貼這一段吧,其他兩段代碼無非就是把HashMap換掉,通過改變Max值就行對比。

技術分享圖片 Paste_Image.png

分析:從結果上來看,數據量小的時候,差異並不大(當然了,數據量小,時間基準小,內容太多,就不貼數據表了,確實差異不大),當數據量大於5000左右,SparseArray,最快,HashMap最慢,乍一看,好像SparseArray是最快的,但是要註意,這是順序插入的。也就是SparseArray和Arraymap最理想的情況。

來個逆序插入的試試

long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {    
hash.put(MAX-1-i, i+"");
}
long ts = System.currentTimeMillis() - start;
技術分享圖片 Paste_Image.png

分析:從結果上來看,果然,HashMap遠超Arraymap和SparseArray,也前面分析一致。
當然了,數據量小的時候,例如1000以下,這點時間差異也是可以忽略的。

下面來看看空間對比:先說一下測試方法,因為測試內存,所以尤其要註意的一點,就是測試的過程不要發生GC,如果發生了GC,那數據就不準了,想了想,用了個比較簡單的方法:

Runtime.getRuntime().totalMemory()//獲取應用已經申請到的總的內存
Runtime.getRuntime().freeMemory()//獲取應用內存的free部分

兩個方法的差值就是應用已經使用的內存部分。

技術分享圖片 Paste_Image.png

值得註意的是當MAX值很大的時候,可能在代碼執行過程發生GC,此時可以同時用Android Monitor的Memory窗口監視內存,沒有發生gc的過程結果才有效。假設數據量比較大的時候,每測完一次手動GC一次,這樣基本上每次都能測試成功;因為數據量也不是特別大,只有很少一部分情況測試過程會發生GC,所以也沒有去進一步探究其他方式,比如設置虛擬機參數來延長GC時間,有空了可以搞一下。上數據:

技術分享圖片 Paste_Image.png

可見,SparseArray在內存占用方面的確要優於HashMap和ArrayMap不少,通過數據觀察,大致節省30%左右,而ArrayMap的表現正如前面說的,優化作用有限,幾乎和HashMap相同。

2.查找性能對比

long start = System.currentTimeMillis();    
SparseArray<String> hash = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {   
 hash.get(i);
}
long ts = System.currentTimeMillis() - start;
技術分享圖片 Paste_Image.png

發現SparseArray比HashMap要快,和前面假設的不符,二分查找難道比Hash快?
再一想,因為用這樣的代碼測試有點不公平,因為SparseArray沒有裝箱,HashMap有個裝箱的過程,似乎不太公平。那麽想個辦法再來測試下,

ArrayList<IntEntity> intEntityList=new ArrayList<IntEntity>();
private void boxing(){  
  for(int i=0;i<MAX;i++){      
  IntEntity entity=new IntEntity();  
      entity.i1=i;        
    entity.i2=Integer.valueOf(i);       
 intEntityList.add(entity);  
  }
}
class IntEntity{    
 int i1;   
 Integer i2;
}

給HashMap和ArrayMap的時候給它提前裝箱,這樣似乎公平些。

long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) { 
 //  hash.get(i); 
  hash.get(intEntityList.get(i).i2);
}
long ts = System.currentTimeMillis() - start;
技術分享圖片 Paste_Image.png

果然結果不一樣了,HashMap才是查詢最快的,這才符合邏輯嘛,但是我們正常用的時候是不管裝不裝箱的,所以綜合起來還是使用SparseArray效率最高。

扯了這麽多,終於到了該總結的時候了。
四、總結
1.在數據量小的時候一般認為1000以下,當你的key為int的時候,使用SparseArray確實是一個很不錯的選擇,內存大概能節省30%,相比用HashMap,因為它key值不需要裝箱,所以時間性能平均來看也優於HashMap,建議使用!
2.ArrayMap相對於SparseArray,特點就是key值類型不受限,任何情況下都可以取代HashMap,但是通過研究和測試發現,ArrayMap的內存節省並不明顯,也就在10%左右,但是時間性能確是最差的,當然了,1000以內的數據量也無所謂了,加上它只有在API>=19才可以使用,個人建議沒必要使用!還不如用HashMap放心。估計這也是為什麽我們再new一個HashMap的時候google也沒有提示讓我們使用的原因吧。

【轉】HashMap,ArrayMap,SparseArray源碼分析及性能對比