使用 Android Studio 做爲 IDE 的開發者可能會遇到一個現象,就是在代碼中若是聲明瞭 Map<Integer, Object>
類型的變量的話,Android Studio 會提示:Use new SparseArray<Object>(...) instead for better performance ...
,意思就是用 SparseArray< Object > 性能更優,能夠用來替代 HashMapjava
這裏就來介紹下 SparseArray 的內部原理,看看它與 HashMap 有什麼差異,關於 HashMap 的源碼解析能夠看這裏:Java集合框架源碼解析之HashMapandroid
先看下 SparseArray 的使用方式git
SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(100, "leavesC");
sparseArray.remove(100);
sparseArray.get(100);
sparseArray.removeAt(29);
複製代碼
SparseArray< E > 至關於 Map< Integer,E > ,key 值固定爲 int 類型,在初始化時只須要聲明 Value 的數據類型便可,其內部用兩個數組分別來存儲 Key 列表和 Value 列表:int[] mKeys ; Object[] mValues
github
mKeys
和 mValues
經過以下方式對應起來:數組
SparseArray
存入 key
爲 10
,value
爲 200
的鍵值對,則先將 10
存到 mKeys
中,假設 10
在 mKeys
中對應的索引值是 index
,則將 value
存入 mValues[index]
中最重要的一點就是 SparseArray 避免了 Map 每次存取值時的裝箱拆箱操做,Key 值都是基本數據類型 int,這有利於提高性能框架
布爾變量 mGarbage
也是 SparseArray 的一個優化點之一,用於標記當前是否有待垃圾回收(GC)的元素,當該值被置爲 true 時,即意味着當前狀態須要進行垃圾回收,但回收操做並不立刻進行,而是在後續操做中再統一進行ide
//數組元素在沒有外部指定值時的默認元素值
private static final Object DELETED = new Object();
//用於標記當前是否有待垃圾回收(GC)的元素
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
//當前集合元素大小
//該值並不必定是時時處於正確狀態,由於有可能出現只刪除 key 和 value 二者之一的狀況
//因此在調用 size() 方法前都須要進行 GC
private int mSize;
複製代碼
key 數組和 value 數組的默認大小都是 10,若是在初始化時已知數據量的預估大小,則能夠直接指定初始容量,這樣能夠避免後續的擴容操做函數
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
複製代碼
添加元素的方法有幾個,主要看 put(int key, E value)
方法,當中用到了 ContainerHelpers
類提供的二分查找方法:binarySearch
,用於查找目標 key 在 mKeys 中的當前索引(已有改 key)或者是目標索引(沒有該 key)性能
binarySearch 方法的返回值分爲兩種狀況:學習
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
}
複製代碼
能夠看到,即便在 mKeys 中不存在目標 key,但其返回值也指向了應該讓 key 存入的位置。經過將計算出的索引值進行 ~ 運算,則返回值必定是 0 或者負數,從而與「找獲得目標key的狀況(返回值大於0)」的狀況區分開
從這個能夠看出該方法的巧妙之處,單純的一個返回值就能夠區分出多種狀況,且經過這種方式來存放數據可使得 mKeys 的內部值一直是按照值遞增的方式來排序的
public void put(int key, E value) {
//用二分查找法查找指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找獲得則直接賦值
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
//若是目標位置還未賦值,則直接存入數據便可,對應的狀況是 2.1
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//如下操做對應 2.一、2.2 兩種狀況:
if (mGarbage && mSize >= mKeys.length) {
gc();
//GC 後再次進行查找,由於值可能已經發生變化了
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//經過複製或者擴容數組,將數據存放到數組中
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
複製代碼
上文說了,布爾變量 mGarbage
用於標記當前是否有待垃圾回收(GC)的元素,當該值被置爲 true 時,即意味着當前狀態須要進行垃圾回收,但回收操做並不立刻進行,而是在後續操做中再完成
如下幾個方法在移除元素時,都是隻切斷了 mValues 中的引用,而 mKeys 並無進行回收,這個操做會留到 gc()
進行處理
//若是存在 key 對應的元素值,則將其移除
public void delete(int key) {
//用二分查找法查找指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
//標記當前須要進行垃圾回收
mGarbage = true;
}
}
}
//和 delete 方法基本相同,差異在於會返回 key 對應的元素值
public E removeReturnOld(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
final E old = (E) mValues[i];
mValues[i] = DELETED;
mGarbage = true;
return old;
}
}
return null;
}
//省略其它幾個比較簡單的移除元素的方法
複製代碼
查找元素的方法較多,但邏輯都是挺簡單的
//根據 key 查找相應的元素值,查找不到則返回默認值
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
//用二分查找法查找指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//若是找不到該 key 或者該 key 還沒有賦值,則返回默認值
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
//根據 value 查找對應的索引值
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (mValues[i] == value) {
return i;
}
}
return -1;
}
//與 indexOfValue 方法相似,但 indexOfValue 方法是經過比較 == 來判斷是否同個對象
//而此方法是經過 equals 方法來判斷是否同個對象
public int indexOfValueByValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (value == null) {
if (mValues[i] == null) {
return i;
}
} else {
if (value.equals(mValues[i])) {
return i;
}
}
}
return -1;
}
//省略其它幾個方法
複製代碼
由於 SparseArray 中可能會出現只移除 value 和 value 二者之一的狀況,致使數組中存在無效引用,所以 gc()
方法就用於移除無效引用,並將有效的元素值位置合併在一塊兒
private void gc() {
int n = mSize;
//o 值用於表示 GC 後的元素個數
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
//元素值非默認值 DELETED ,說明該位置可能須要移動數據
if (val != DELETED) {
//如下代碼片斷用於將索引 i 處的值賦值到索引 o 處
//因此若是 i == o ,則不須要執行代碼了
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
}
複製代碼
從上文的解讀來看,SparseArray 的主要優點有如下幾點:
劣勢有如下幾點:
篇幅所限,這裏就不粘貼處 SparseArray.java 的完整詳細源碼註解了,能夠點擊這裏查看:SparseArray.java