Android內存優化(使用SparseArray和ArrayMap代替HashMap)

轉自

Android開發時,我們使用的大部分都是Java的api,比如HashMap這個api,使用率非常高,但是對于android這種對內存非常敏感的移動平臺,很多時候使用一些java的api并不能達到更好的性能,相反反而更消耗內存,所以針對Android這種移動平臺,也推出了更符合自己的api,比如SparseArray、ArrayMap用來代替HashMap在有些情況下能帶來更好的性能提升。

介紹它們之前先來介紹一下HashMap的內部存儲結構,就明白為什么推薦使用SparseArray和ArrayMap

HashMap

HashMap內部是使用一個默認容量為16的數組來存儲數據的,而數組中每一個元素卻又是一個鏈表的頭結點,所以,更準確的來說,HashMap內部存儲結構是使用哈希表的拉鏈結構(數組+鏈表),如圖:

這種存儲數據的方法叫做拉鏈法

且每一個結點都是Entry類型,那么Entry是什么呢?我們來看看HashMap中Entry的屬性:

finalK key;V value;finalinthash;HashMapEntry next;

從中我們得知Entry存儲的內容有key、value、hash值、和next下一個Entry,那么,這些Entry數據是按什么規則進行存儲的呢?就是通過計算元素key的hash值,然后對HashMap中數組長度取余得到該元素存儲的位置,計算公式為hash(key)%len,比如:假設hash(14)=14,hash(30)=30,hash(46)=46,我們分別對len取余,得到

hash(14)%16=14,hash(30)%16=14,hash(46)%16=14,所以key為14、30、46的這三個元素存儲在數組下標為14的位置,如:

從中可以看出,如果有多個元素key的hash值相同的話,后一個元素并不會覆蓋上一個元素,而是采取鏈表的方式,把之后加進來的元素加入鏈表末尾,從而解決了hash沖突的問題,由此我們知道HashMap中處理hash沖突的方法是鏈地址法,在此補充一個知識點,處理hash沖突的方法有以下幾種:

開放地址法

再哈希法

鏈地址法

建立公共溢出區

講到這里,重點來了,我們知道HashMap中默認的存儲大小就是一個容量為16的數組,所以當我們創建出一個HashMap對象時,即使里面沒有任何元素,也要分別一塊內存空間給它,而且,我們再不斷的向HashMap里put數據時,當達到一定的容量限制時(這個容量滿足這樣的一個關系時候將會擴容:HashMap中的數據量>容量*加載因子,而HashMap中默認的加載因子是0.75),HashMap的空間將會擴大,而且擴大后新的空間一定是原來的2倍,我們可以看put()方法中有這樣的一行代碼:

int newCapacity=oldCapacity * 2;

1

1

所以,重點就是這個,只要一滿足擴容條件,HashMap的空間將會以2倍的規律進行增大。假如我們有幾十萬、幾百萬條數據,那么HashMap要存儲完這些數據將要不斷的擴容,而且在此過程中也需要不斷的做hash運算,這將對我們的內存空間造成很大消耗和浪費,而且HashMap獲取數據是通過遍歷Entry[]數組來得到對應的元素,在數據量很大時候會比較慢,所以在Android中,HashMap是比較費內存的,我們在一些情況下可以使用SparseArray和ArrayMap來代替HashMap。

SparseArray

SparseArray比HashMap更省內存,在某些條件下性能更好,主要是因為它避免了對key的自動裝箱(int轉為Integer類型),它內部則是通過兩個數組來進行數據存儲的,一個存儲key,另外一個存儲value,為了優化性能,它內部對數據還采取了壓縮的方式來表示稀疏數組的數據,從而節約內存空間,我們從源碼中可以看到key和value分別是用數組表示:

privateint[] mKeys;privateObject[] mValues;

我們可以看到,SparseArray只能存儲key為int類型的數據,同時,SparseArray在存儲和讀取數據時候,使用的是二分查找法,我們可以看看:

public void put(int key, E value) {? ? ? ? int i = ContainerHelpers.binarySearch(mKeys, mSize, key);...} public E get(int key, E valueIfKeyNotFound) {? ? ? ? int i = ContainerHelpers.binarySearch(mKeys, mSize, key);...}

也就是在put添加數據的時候,會使用二分查找法和之前的key比較當前我們添加的元素的key的大小,然后按照從小到大的順序排列好,所以,SparseArray存儲的元素都是按元素的key值從小到大排列好的。

而在獲取數據的時候,也是使用二分查找法判斷元素的位置,所以,在獲取數據的時候非常快,比HashMap快的多,因為HashMap獲取數據是通過遍歷Entry[]數組來得到對應的元素。

添加數據

publicvoidput(intkey, Evalue)

刪除數據

publicvoidremove(intkey)

or

publicvoiddelete(intkey)

其實remove內部還是通過調用delete來刪除數據的

獲取數據

publicEget(intkey)

or

publicEget(intkey, E valueIfKeyNotFound)

該方法可設置如果key不存在的情況下默認返回的value

特有方法

在此之外,SparseArray還提供了兩個特有方法,更方便數據的查詢:

獲取對應的key:

publicintkeyAt(intindex)

獲取對應的value:

publicEvalueAt(intindex)

SparseArray應用場景:

雖說SparseArray性能比較好,但是由于其添加、查找、刪除數據都需要先進行一次二分查找,所以在數據量大的情況下性能并不明顯,將降低至少50%。

滿足下面兩個條件我們可以使用SparseArray代替HashMap:

數據量不大,最好在千級以內

key必須為int類型,這中情況下的HashMap可以用SparseArray代替:

HashMap map =newHashMap<>();用SparseArray代替:SparseArray array =newSparseArray<>();

ArrayMap

這個api的資料在網上可以說幾乎沒有,然并卵,只能看文檔了

ArrayMap是一個<key,value>映射的數據結構,它設計上更多的是考慮內存的優化,內部是使用兩個數組進行數據存儲,一個數組記錄key的hash值,另外一個數組記錄Value值,它和SparseArray一樣,也會對key使用二分法進行從小到大排序,在添加、刪除、查找數據的時候都是先使用二分查找法得到相應的index,然后通過index來進行添加、查找、刪除等操作,所以,應用場景和SparseArray的一樣,如果在數據量比較大的情況下,那么它的性能將退化至少50%。

添加數據

publicVput(K key, Vvalue)

獲取數據

publicVget(Objectkey)

刪除數據

publicV remove(Objectkey)

1

1

特有方法

它和SparseArray一樣同樣也有兩個更方便的獲取數據方法:

publicKkeyAt(intindex)publicVvalueAt(intindex)

ArrayMap應用場景

數據量不大,最好在千級以內

數據結構類型為Map類型

ArrayMaparrayMap = new ArrayMap<>();

【注】:如果我們要兼容aip19以下版本的話,那么導入的包需要為v4包

import android.support.v4.util.ArrayMap;

總結

SparseArray和ArrayMap都差不多,使用哪個呢?

假設數據量都在千級以內的情況下:

1、如果key的類型已經確定為int類型,那么使用SparseArray,因為它避免了自動裝箱的過程,如果key為long類型,它還提供了一個LongSparseArray來確保key為long類型時的使用

2、如果key類型為其它的類型,則使用ArrayMap

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容