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

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也沒有提示讓我們使用的原因吧。



作者:jjlanbupt
鏈接:http://www.jianshu.com/p/7b9a1b386265
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
發佈了88 篇原創文章 · 獲贊 18 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章