系列文章地址:
Android容器類-ArraySet原理解析(一)
Android容器類-ArrayMap原理解析(二)
Android容器類-SparseArray原理解析(三)
Android容器類-SparseIntArray原理解析(四)
相較於其他裝置,移動裝置有自己的特點,記憶體小是一個很突出的問題,Google針對android裝置的這一特點,開發了一套容器框架,目的就是為了更加高效地利用記憶體。接下來就對這些容器進行一下總結。
組織結構
以上是android中容器的實現繼承結構,簡單梳理一下:ArraySet
實現了Set
和Collections
介面,在api 23中新增ArrayMap
實現了Map
介面,在api 19中新增SparseArray
,SparseIntArray
,SparseBooleanArray
實現了Cloneable
介面,在api 1中新增SparseLong
實現了Cloneable
介面,在api 18中新增
分類
從**功能
**上劃分,可以將以上容器劃分為兩類:
-
儲存元素
ArraySet
優化了HashSet
對元素的儲存 -
儲存鍵值對 相較於
HashMap
,具體的優化方向如下:
ArrayMap
優化了HashMap
儲存Object --> Object
的鍵值儲存;
SparseArray
優化了int --> Object
的鍵值儲存;
SparseIntArray
優化了int --> int
的鍵值儲存;
SparseBooleanArray
優化了int --> boolean
的鍵值儲存;
SparseLongArray
優化了int --> long
的鍵值儲存。
優化方法
從組織結構可以看出,可以將這些容器分為3類:ArraySet
,ArrayMap
和剩餘的容器。通過前面的分析可以知道,ArraySet
和ArrayMap
使用的相同的優化方式,SparseArray
在進行優化的時候使用gc
垃圾回收策略,故從優化方法上進行分類的話可以分一下三類:
ArraySet
,ArrayMap
使用陣列mKeys
儲存key
的hash值,hash值在mKeys
的位置為index
,並將value儲存到mValues
陣列對應下標的位置(ArrayMap
中key
和value
分別在mValues
的index * 2
和index * 2 + 1
的位置)。查詢或者修改元素時,使用二分查詢在mKeys
中找到元素在mValues
的下標,然後進行修改或者返回。SparseArray
使用int
型別的mKeys
陣列儲存int
型別的鍵,下標為index
,將Object
型別的value
儲存在在Object
型別的陣列mValues
的index
位置,在查詢和修改時,使用二分查詢在mKeys
中找到元素在mValues
的下標,然後進行修改或者返回。在刪除value
時,SparseArray
並不直接進行陣列元素的移動,而是將待刪除的value
標記為DELETED
狀態,在gc
的過程中將所有非DELETED
狀態的元素移動到陣列的最前面,從而減少二分查詢的時間。SparseIntArray
,SparseLongArray
,SparseBooleanArray
這3個容器可以理解成專用容器,使用int
型別陣列和對應型別的陣列;使用二分查詢快速查詢元素,然後進行刪除,修改,新增操作。
優化共同點與差異
雖然這些容器儲存的元素型別不同,但是通過分析可以發現他們在記憶體優化中的共同點,接下來就分析下這些容器在優化上存在的共同點和差異。
共同點
-
資料結構
這裡的資料結構是指容器的底層儲存結構,雖然在ArrayMap
中mValues的長度是mKeys的2倍,但也僅僅是陣列長度上的差異,底層儲存使用的思想仍然是一樣的;int
型別的陣列mKeys
裡的元素時按照升序進行排列的。相較於HashMap
使用Node
結構儲存,這樣的儲存方式使用更小的儲存空間儲存k-v
,同時避免了原始資料型別的自動裝箱。 -
查詢方法 在組織結構中列出的容器,他們在進行元素查詢時,都會先在
mKeys
陣列中利用二分查詢找到元素的下標index
,然後使用index
到mValues
陣列中對value
進行操作。 -
獲取帶插入下標 在進行元素插入時,會首先使用二分查詢在
mKeys
陣列中查詢元素的下標,如果元素不存在,則二分查詢會返回元素待插入位置的取反。
不同點
- 對
key
的處理ArraySet
,ArrayMap
底層實現時,會計算待插入元素的hash值,根據hash值,在mKeys
找到待插入位置;SparseArray
和SparseXXXArray
儲存的時候直接使用key
值,不會進行hash計算。 - 對
null
的處理ArraySet
和ArrayMap
允許插入key
為null
的元素,key
的hash值為0;SparseArray
和SparseXXXArray
儲存的時由於直接使用int型別的資料作為key
,故不存在key
為null
的情況。 - 快取
為了避免頻繁的記憶體回收,
ArraySet
和ArrayMap
新增了快取結構,SparseArray
和SparseXXXArray
沒有快取 - 擴容規則
ArraySet
和ArrayMap
在進行擴容的時,容量的變化規則為4, 8 , size * 2 / 3
,SparseArray
和SparseXXXArray
使用ArrayUtils.newUnpaddedArray
建立新的資料,將原來的資料拷貝到新陣列中。
使用建議
雖然這些容器在Android裝置上可以更高效地利用記憶體,但是還是存在使用使用限制。
- 相容性 在組織結構中,可以看到,並不是所有的容器都是從api 1就開始提供的,在使用具體的容器時,需要考慮應用的相容。
- 對效能的影響
雖然
ArrayMap
在刪除時不直接使用移動元素的方式刪除元素,但是在獲取陣列元素等操作中還是 - 對數量的限制
在對元素進行查詢時會使用二分查詢,元素數量較大(超過1000)時,查詢效率會降低,相較於
HashMap
只要數量不超過1000,效率最多不會下降50%。
總結
前面說了很多,其實android容器優化的根本思想就是使用int
到其他型別的對映,使用陣列儲存著兩個對映,用以優化HashMap
對k-v
的儲存。這種優化適用於元素數量較少(少於1000)的情況。