Android原始碼分析–ArrayMap優化

Cang_Wang發表於2019-02-26

以下連結是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。

[Android]如何做一個崩潰率少於千分之三噶應用app–章節列表

如果有看關注過我ModuleMap裡面的原始碼,你會發現我從將裡面的HashMap的資料結構,換成了ArrayMap了。

ModuleBus地址

ArrayMap是Android系統獨有封裝的,我們要在4.4以前運用,要使用v4的包相容來獲取ArrayMap。

要了解ArrayMap,需要大家先去了解HashMap。

HashMap基於雜湊表的Map介面的實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵。(除了不同步和允許使用 null 之外,HashMap 類與Hashtable大致相同)此類不保證對映的順序,特別是它不保證該順序恆久不變。

值得注意的是HashMap不是執行緒安全的,如果想要執行緒安全的HashMap,可以通過Collections類的靜態方法synchronizedMap獲得執行緒安全的HashMap。

“`

Map map= Collections.synchronizedMap(newHashMap());

“`

二、HashMap資料結構

HashMap的底層主要是基於陣列和連結串列來實現的,它之所以有相當快的查詢速度主要是因為它是通過計算雜湊碼來決定儲存的位置。HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果儲存的物件對多了,就有可能不同的物件所算出來的hash值是相同的,這就出現了所謂的hash衝突。學過資料結構的同學都知道,解決hash衝突的方法有很多,HashMap底層是通過連結串列來解決hash衝突的。

Android原始碼分析–ArrayMap優化

圖中,紫色部分即代表雜湊表,也稱為雜湊陣列,陣列的每個元素都是一個單連結串列的頭節點,連結串列是用來解決衝突的,如果不同的key對映到了陣列的同一位置處,就將其放入單連結串列中。

HashMap的基礎構造器HashMap(int initialCapacity, float loadFactor)帶有兩個引數,它們是初始容量initialCapacity和載入因子loadFactor。

initialCapacity:HashMap的最大容量,即為底層陣列的長度。

loadFactor:負載因子loadFactor定義為:雜湊表的實際元素數目(n)/ 雜湊表的容量(m)。

負載因子衡量的是一個雜湊表的空間的使用程度,負載因子越大表示雜湊表的裝填程度越高,反之愈小。對於使用連結串列法的雜湊表來說,查詢一個元素的平均時間是O(1+a),因此如果負載因子越大,對空間的利用更充分,然而後果是查詢效率的降低;如果負載因子太小,那麼雜湊表的資料將過於稀疏,對空間造成嚴重浪費。

一個HashMap 初始容量為 16,負載因子為 0.75

只要一滿足擴容條件,HashMap的空間將會以2倍的規律進行增大。

HashMap基礎的結構介紹到這裡。

為何要使用ArrayMap?

我們可以很清晰看到ArrayMap是繼承於SimpleArrayMap,SimpleArrayMap才是其真正的實現,而通過Map提供的介面包裝暴露方法

Android原始碼分析–ArrayMap優化

其真正實現類是SimpleArrayMap。

1、儲存方式不同

HashMap內部有一個HashMapEntry[]物件,每一個鍵值對都儲存在這個物件裡,當使用put方法新增鍵值對時,就會new一個HashMapEntry物件

ArrayMap的儲存中沒有Entry這個東西,他是由兩個陣列來維護的

Android原始碼分析–ArrayMap優化

mHashes陣列中儲存的是每一項的HashCode值,mArray中就是鍵值對,每兩個元素代表一個鍵值對,前面儲存key,後面的儲存value。

Android原始碼分析–ArrayMap優化

從程式碼檢視put的方法可以清晰看到,mHashes儲存的是hash值,而mArray儲存的相鄰兩個值儲存的是key和value。

2、新增資料時擴容時的處理不一樣

HashMap使用New的方式申請空間,並返回一個新的物件,開銷會比較大

ArrayMap用的是System.arrayCopy資料,所以效率相對要高。

初始化計算是否滿足最小需要的容量,然後去擴容,可以看出ArrayMap是使用System.arrayCopy來移動陣列的。

Android原始碼分析–ArrayMap優化

3、ArrayMap提供了陣列收縮的功能,只要判斷過判斷容量尺寸,例如clear,put,remove等方法,只要通過判斷size大小觸發到freeArrays或者allocArrays方法,會重新收縮陣列,是否空間。

Android原始碼分析–ArrayMap優化

Android原始碼分析–ArrayMap優化

申請陣列,對於BASE_SIZE*2和BASE_SIZE兩種尺寸的陣列在這裡它並沒有對它們進行釋放,而是把它們快取起來,這樣我們在分配的時候,如果需要分配這兩種大小的陣列,就可以直接從快取中取得,否則,就直接new兩個陣列,第二個陣列存放的是鍵值對,所以大小是size的兩倍,size<<1左移一位操作就相當於乘以2

Android原始碼分析–ArrayMap優化

釋放陣列

Android原始碼分析–ArrayMap優化

4、ArrayMap相比傳統的HashMap速度要慢,因為查詢方法是二分法,並且當你刪除或者新增資料時,會對空間重新調整,在使用大量資料時,效率低於50%。可以說ArrayMap是犧牲了時間換區空間。但在寫手機app時,適時的使用ArrayMap,會給記憶體使用帶來可觀的提升。ArrayMap內部還是按照正序排列的,這時因為ArrayMap在檢索資料的時候使用的是二分查詢,所以每次插入新資料的時候ArrayMap都需要重新排序,逆序是最差情況;

get的方法會呼叫indexOfKey

Android原始碼分析–ArrayMap優化

indexOfKey實際是呼叫indexOf方法,然後再呼叫ContainerHelpers裡面的二分法查詢的方法

Android原始碼分析–ArrayMap優化

通過二分法查詢到index

Android原始碼分析–ArrayMap優化

如果使用HashMap,當事件個數不斷加大,那麼更加會產生大量空餘的記憶體。

在記憶體和計算速度的取捨,在移動端來說,記憶體比較金貴。

暫時就介紹到這裡了。

下一節敬請期待!!!

相關文章