Android容器類小結

奇舞移動發表於2018-11-12

系列文章地址:
Android容器類-ArraySet原理解析(一)
Android容器類-ArrayMap原理解析(二)
Android容器類-SparseArray原理解析(三)
Android容器類-SparseIntArray原理解析(四)

相較於其他裝置,移動裝置有自己的特點,記憶體小是一個很突出的問題,Google針對android裝置的這一特點,開發了一套容器框架,目的就是為了更加高效地利用記憶體。接下來就對這些容器進行一下總結。

組織結構

image
以上是android中容器的實現繼承結構,簡單梳理一下:

  • ArraySet實現了SetCollections介面,在api 23中新增
  • ArrayMap實現了Map介面,在api 19中新增
  • SparseArraySparseIntArraySparseBooleanArray實現了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和剩餘的容器。通過前面的分析可以知道,ArraySetArrayMap使用的相同的優化方式,SparseArray在進行優化的時候使用gc垃圾回收策略,故從優化方法上進行分類的話可以分一下三類:

  • ArraySet, ArrayMap 使用陣列mKeys儲存key的hash值,hash值在mKeys的位置為index,並將value儲存到mValues陣列對應下標的位置(ArrayMapkeyvalue分別在mValuesindex * 2index * 2 + 1的位置)。查詢或者修改元素時,使用二分查詢在mKeys中找到元素在mValues的下標,然後進行修改或者返回。
  • SparseArray 使用int型別的mKeys陣列儲存int型別的鍵,下標為index,將Object型別的value儲存在在Object型別的陣列mValuesindex位置,在查詢和修改時,使用二分查詢在mKeys中找到元素在mValues的下標,然後進行修改或者返回。在刪除value時,SparseArray並不直接進行陣列元素的移動,而是將待刪除的value標記為DELETED狀態,在gc的過程中將所有非DELETED狀態的元素移動到陣列的最前面,從而減少二分查詢的時間。
  • SparseIntArray, SparseLongArray, SparseBooleanArray 這3個容器可以理解成專用容器,使用int型別陣列和對應型別的陣列;使用二分查詢快速查詢元素,然後進行刪除,修改,新增操作。

優化共同點與差異

雖然這些容器儲存的元素型別不同,但是通過分析可以發現他們在記憶體優化中的共同點,接下來就分析下這些容器在優化上存在的共同點和差異。

共同點

  • 資料結構

    image
    這裡的資料結構是指容器的底層儲存結構,雖然在ArrayMap中mValues的長度是mKeys的2倍,但也僅僅是陣列長度上的差異,底層儲存使用的思想仍然是一樣的;int型別的陣列mKeys裡的元素時按照升序進行排列的。相較於HashMap使用Node結構儲存,這樣的儲存方式使用更小的儲存空間儲存k-v,同時避免了原始資料型別的自動裝箱。

  • 查詢方法 在組織結構中列出的容器,他們在進行元素查詢時,都會先在mKeys陣列中利用二分查詢找到元素的下標index,然後使用indexmValues陣列中對value進行操作。

  • 獲取帶插入下標 在進行元素插入時,會首先使用二分查詢在mKeys陣列中查詢元素的下標,如果元素不存在,則二分查詢會返回元素待插入位置的取反。

不同點

  • key的處理 ArraySet, ArrayMap底層實現時,會計算待插入元素的hash值,根據hash值,在mKeys找到待插入位置;SparseArraySparseXXXArray儲存的時候直接使用key值,不會進行hash計算。
  • null的處理 ArraySetArrayMap允許插入keynull的元素,key的hash值為0;SparseArraySparseXXXArray儲存的時由於直接使用int型別的資料作為key,故不存在keynull的情況。
  • 快取 為了避免頻繁的記憶體回收,ArraySetArrayMap新增了快取結構,SparseArraySparseXXXArray沒有快取
  • 擴容規則 ArraySetArrayMap在進行擴容的時,容量的變化規則為4, 8 , size * 2 / 3SparseArraySparseXXXArray使用ArrayUtils.newUnpaddedArray建立新的資料,將原來的資料拷貝到新陣列中。

使用建議

雖然這些容器在Android裝置上可以更高效地利用記憶體,但是還是存在使用使用限制。

  • 相容性 在組織結構中,可以看到,並不是所有的容器都是從api 1就開始提供的,在使用具體的容器時,需要考慮應用的相容。
  • 對效能的影響 雖然ArrayMap在刪除時不直接使用移動元素的方式刪除元素,但是在獲取陣列元素等操作中還是
  • 對數量的限制 在對元素進行查詢時會使用二分查詢,元素數量較大(超過1000)時,查詢效率會降低,相較於HashMap只要數量不超過1000,效率最多不會下降50%。

總結

前面說了很多,其實android容器優化的根本思想就是使用int到其他型別的對映,使用陣列儲存著兩個對映,用以優化HashMapk-v的儲存。這種優化適用於元素數量較少(少於1000)的情況。

關注微信公眾號,最新技術乾貨實時推送

image

相關文章