SparseArray
的用法和key
爲int
類型,value
爲Object
類型的HashMap
相同,和HashMap
相比,先簡要介紹一下它的兩點優點。java
在 Java&Android 基礎知識梳理(8) - 容器類 咱們已經學習過HashMap
的內部實現,它內部是採用數組的形式保存每一個Entry
,並採用鏈地址法來解決Hash
衝突的問題。可是採用數組會遇到擴容的問題,默認狀況下當數組內的元素達到loadFactor
的時候,會將其擴大爲目前大小的兩倍,那麼就有可能形成空間的浪費。數組
SparseArray
雖然也是採用數組的方式來保存Key/Value
學習
private int[] mKeys;
private Object[] mValues;
複製代碼
可是與HashMap
使用普通數組不一樣,它對存放Value
的mValues
數組進行了優化,其建立方式爲:優化
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
//默認狀況下,建立的 initialCapacity 大小爲 10。
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
複製代碼
其中ArrayUtils.newUnpaddedObjectArray(initialCapacity)
用於建立優化後的數組,該方法其實是一個Native
方法,它解決了當數組中的元素沒有填滿時形成的空間浪費。spa
在 SparseArray 淺析 一文中介紹了SparseArray
對於數組的優化方式,假設有一個9 x 7
的數組,在通常狀況下它的存儲模型能夠表示以下:.net
能夠看到這種模型下的數組當中存在大量無用的0
值,內存利用率很低。而優化後的方案用兩個部分來表示數組:指針
mKeys
則是用普通數組實現的,經過查找
Key
值所在的位置,再根據
mValues
數組的屬性找到對應元素的行、列值,從而獲得對應的元素值。
對於HashMap
來講,當咱們採用put(1, Object)
這樣的形式來放入一個元素時,會進行自動裝箱,即建立一個Integer
對象放入到Entry
當中。code
SparseArray
則不會存在這一問題,由於咱們聲明的就是int[]
類型的mKeys
數組。cdn
public void put(int key, E value) {
//經過二分查找法進行查找插入元素所在位置。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//若是大於0,那麼直接插入。
if (i >= 0) {
mValues[i] = value;
} else {
//找到插入的位置。
i = ~i;
//若是插入的位置以前已經分配,可是該位置上的元素已經被標記爲刪除,那麼直接替換。
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//首先回收掉以前標記爲刪除位置的元素。
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//從新分配數組,並插入新的 Key,Value。
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
複製代碼
public E get(int key, E valueIfKeyNotFound) {
//經過二分查找,在 Key 數組中獲得對應 Value 的下標。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//取出下標對應的元素。
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
複製代碼
public void delete(int key) {
//二分查找所在位置。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//將該位置的元素置爲 DELETED,它是內部預先定義好的一個對象。
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
複製代碼
能夠看到,在刪除元素的時候,它是用一個空的Object
來標記該位置。在合適的時候(例如上面的put
方法),才經過下面的gc()
方法對mKeys
和mValues
數組 從新排列。對象
private void gc() {
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
}
複製代碼
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
final int midVal = array[mid];
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
}
//若是沒有找到,那麼返回的是低位指針的取反座標。
return ~lo; // value not present
}
複製代碼
這裏用的是~
,因爲lo>=0
,因此當沒法查找到對應元素的時候,返回值~lo
必定<0
。(~lo=-(lo+1)
)
這也是咱們在2.1
中看到,爲何在i>=0
時就能夠直接替換的緣由,由於只要i>=0
,就說明以前已經存在一個Key
相同的元素了。
而在返回值小於0
時,對它再一次取~
,就恰好能夠獲得 要插入的位置。
瞭解了SparseArray
的原理以後,咱們能夠分析出有如下幾方面有可能會影響SparseArray
插入的效率:
Key
值插入的前後順序有關,假如Key
值是按 遞減順序 插入的,那麼每次咱們都是在mValues
的[0]
位置插入元素,這就要求把原來Values
和mKeys
數組中[0, xxx]
位置元素複製到[1, xxx+1]
的位置,而若是是 遞增插入 的則不會存在該問題,直接擴大數組數組的範圍以後再插入便可。Key
值位於折半處,那麼將會更快地找到對應的元素。也就是說SparseArray
在插入和查找上,相對於HashMap
並不存在明顯的優點,甚至在某些狀況下,效率還要更差一些。
Google
之因此推薦咱們使用SparseArray
來替換HashMap
,是由於在移動端咱們的數據集每每都是比較小的,而在這種狀況下,這二者效率的差異幾乎能夠忽略。可是在內存利用率上,因爲採用了優化的數組結構,而且避免了自動裝箱,SparseArray
明顯更高,所以更推薦咱們使用SparseArray
。
SparseArray
還有幾個衍生的類,它們的基本思想都是同樣的,即:
key
和value
,經過下標管理映射關係。mKeys
數組中對應找到所在元素的下標,再去mValues
數組中取出元素。咱們在平時使用的時候,能夠根據實際的應用場景選取相應的集合類型。
假如key
爲long
型:
LongSparseArray
:key
爲long
,value
爲Object
假如key
爲int
,而value
爲下面三種基本數據類型之一,那麼能夠採用如下三種集合來避免value
的自動裝箱來進一步優化。
SparseLongArray
:key
爲int
,value
爲long
SparseBooleanArray
:key
爲int
,value
爲boolean
SparseIntArray
:key
爲int
,value
爲int
假如key
和value
都不爲基本數據類型,那麼能夠採用:
ArrayMap
:key
爲Object
,value
爲Object