在Android開發時,我們使用的大部分都是Java的api,比方HashMap這個api,使用率非常高,可是對於Android這樣的對內存非常敏感的移動平台,非常多時候使用一些java的api並不能達到更好的性能,相反反而更消耗內存,所以針對Android這樣的移動平台,也推出了更符合自己的api,比方SparseArray、ArrayMap用來取代HashMap在有些情況下能帶來更好的性能提升。
介紹它們之前先來介紹一下HashMap的內部存儲結構。就明確為什么推薦使用SparseArray和ArrayMap
HashMap
HashMap內部是使用一個默認容量為16的數組來存儲數據的,而數組中每個元素卻又是一個鏈表的頭結點。所以,更准確的來說,HashMap內部存儲結構是使用哈希表的拉鏈結構(數組+鏈表),如圖:
這樣的存儲數據的方法叫做拉鏈法
且每個結點都是Entry類型,那么Entry是什么呢?我們來看看HashMap中Entry的屬性:
final K key;
V value;
final int hash;
HashMapEntry<K, V> 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;
所以,重點就是這個,僅僅要一滿足擴容條件,HashMap的空間將會以2倍的規律進行增大。
假如我們有幾十萬、幾百萬條數據,那么HashMap要存儲完這些數據將要不斷的擴容,並且在此過程中也須要不斷的做hash運算,這將對我們的內存空間造成非常大消耗和浪費。並且HashMap獲取數據是通過遍歷Entry[]數組來得到相應的元素,在數據量非常大時候會比較慢,所以在Android中,HashMap是比較費內存的,我們在一些情況下能夠使用SparseArray和ArrayMap來取代HashMap。
SparseArray
SparseArray比HashMap更省內存,在某些條件下性能更好,主要是由於它避免了對key的自己主動裝箱(int轉為Integer類型),它內部則是通過兩個數組來進行數據存儲的。一個存儲key,另外一個存儲value,為了優化性能,它內部對數據還採取了壓縮的方式來表示稀疏數組的數據,從而節約內存空間。我們從源代碼中能夠看到key和value各自是用數組表示:
private int[] mKeys;
private Object[] 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[]數組來得到相應的元素。
加入數據
public void put(int key, E value)
刪除數據
public void remove(int key)
or
public void delete(int key)
事實上remove內部還是通過調用delete來刪除數據的
獲取數據
public E get(int key)
or
public E get(int key, E valueIfKeyNotFound)
該方法可設置假設key不存在的情況下默認返回的value
特有方法
在此之外,SparseArray還提供了兩個特有方法。更方便數據的查詢:
獲取相應的key:
public int keyAt(int index)
獲取相應的value:
public E valueAt(int index)
SparseArray應用場景:
雖說SparseArray性能比較好,可是由於其加入、查找、刪除數據都須要先進行一次二分查找。所以在數據量大的情況下性能並不明顯,將減少至少50%。
滿足下面兩個條件我們能夠使用SparseArray取代HashMap:
- 數據量不大,最好在千級以內
- key必須為int類型,這中情況下的HashMap能夠用SparseArray取代:
HashMap<Integer, Object> map = new HashMap<>();
用SparseArray取代:
SparseArray<Object> array = new SparseArray<>();
ArrayMap
這個api的資料在網上能夠說差點兒沒有,然並卵,僅僅能看文檔了
ArrayMap是一個<key,value>映射的數據結構,它設計上很多其他的是考慮內存的優化,內部是使用兩個數組進行數據存儲,一個數組記錄key的hash值。另外一個數組記錄Value值。它和SparseArray一樣。也會對key使用二分法進行從小到大排序,在加入、刪除、查找數據的時候都是先使用二分查找法得到相應的index,然后通過index來進行加入、查找、刪除等操作,所以,應用場景和SparseArray的一樣,假設在數據量比較大的情況下,那么它的性能將退化至少50%。
加入數據
public V put(K key, V value)
獲取數據
public V get(Object key)
刪除數據
public V remove(Object key)
特有方法
它和SparseArray一樣相同也有兩個更方便的獲取數據方法:
public K keyAt(int index)
public V valueAt(int index)
ArrayMap應用場景
- 數據量不大,最好在千級以內
- 數據結構類型為Map類型
ArrayMap<Key, Value> arrayMap = new ArrayMap<>();
【注】:假設我們要兼容aip19下面版本號的話,那么導入的包須要為v4包
import android.support.v4.util.ArrayMap;
總結
SparseArray和ArrayMap都差點兒相同,使用哪個呢?
假設數據量都在千級以內的情況下:
1、假設key的類型已經確定為int類型。那么使用SparseArray,由於它避免了自己主動裝箱的過程,假設key為long類型,它還提供了一個LongSparseArray來確保key為long類型時的使用
2、假設key類型為其他的類型,則使用ArrayMap
