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

目前本人在公司負責熱修復相關的工作,主要是基于robust的熱修復相關工作。感興趣的同學歡迎進群交流。


image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容