Android開發(fā)中,SparseArray的高效存儲與查找機制詳解
SparseArray
在Android中,SparseArray是一個專門用于存儲稀疏數(shù)據(jù)(大部分元素為null或默認值)的數(shù)組類。常用于存儲與整數(shù)鍵關聯(lián)的對象,其中鍵是原始數(shù)據(jù)類型 int,而不是對象類型 Integer。使得 SparseArray 在內(nèi)存使用上比使用 HashMap<Integer, E> 更高效,因為避免了自動裝箱(autoboxing)和拆箱(unboxing)的開銷。
//E對應HashMap的Value
public class SparseArray<E> implements Cloneable {
// 用來優(yōu)化刪除性能(當有元素被remove delete時),標記已經(jīng)刪除的對象為DELETE
private static final Object DELETED = new Object();
// 用來優(yōu)化刪除性能,標記是否需要垃圾回收
private boolean mGarbage = false;
// 存儲索引,整數(shù)索引(key為整數(shù))從小到大被映射在該數(shù)組
private int[] mKeys;
// 存儲對象(Value)
private Object[] mValues;
// SparseArray實際大小
private int mSize;
public SparseArray() {
//默認容量是10個元素
this(10);
}
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
//mKeys的初值等于new int[0],mValues的初值等于new Object[0]
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
//newUnpaddedObjectArray最后指向了VMRuntime的一個native方法,返回一個至少長initialCapacity的數(shù)組,
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
/**
* 獲得指定key的映射對象,或者null如果沒有該映射。
*/
public E get(int key) {
return get(key, null);
}
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
//二分查找
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
// 如果沒找到或者該value已經(jīng)被標記刪除,則返回默認值
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
// i>0 且該位置的元素未被標記為待刪除,返回該值mValues[i]
return (E) mValues[i];
}
}
public void remove(int key) {
//調(diào)用delete執(zhí)行刪除操作
delete(key);
}
/**
* Removes the mapping from the specified key, if there was any.
*/
/**
* 刪除指定key的映射對象。
*/
public void delete(int key) {
//二分查找
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找到了
if (i >= 0) {
//若未被標記delete,標記為delete,回收mGarbage=true
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
//目的只有一個壓縮空間(壓縮數(shù)組,把無效的值刪除)
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
//循環(huán)整個元素區(qū)間,刪除值為DELETED的數(shù),這里比較巧妙,直接對同一個keys和values操作,完成元素的刪除和移動!
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;//實際大小
// Log.e("SparseArray", "gc end with " + mSize);
}
/**
* 添加一個指定key到指定object的映射,如果之前有一個指定key的映射則直接替換掉原映射object。注意gc。
*/
public void put(int key, E value) {
//先二分查找,確定插入位置,保證了key數(shù)組的有序性
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
//找到了,直接替換
mValues[i] = value;
} else {
// 做一個取反運算,獲得應該插入的index
//沒找到的情況下: i = -insertPoint -1,對他取反剛好得insertPoint。
i = ~i;
//若i在size范圍內(nèi),且剛好對應位置標記為delete了,直接放入
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//若前面if不成立,即i超出了size范圍,或者對應的位置的元素是有效的
// 如果被標記為需要垃圾回收且SparseArray大小不小于keys數(shù)組長度
if (mGarbage && mSize >= mKeys.length) {
// 壓縮空間,會壓縮數(shù)組,把無效的值都去掉,保證連續(xù)有效值
gc();
// Search again because indices may have changed.
// 再次查找插入點因為索引可能改變
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
// 插入,如果size不夠則會重新分配更大的數(shù)組,然后拷貝過去并插入;size足夠則用System.arraycopy把插入位置開始的value都后移然后插入
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
// 實際大小加1
mSize++;
}
}
/**
* Returns the number of key-value mappings that this SparseArray
* currently stores.
*/
//返回mSize,注意gc。
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
}
SparseArray方法:
- put(int key, E value):向數(shù)組中放入一個值。
- get(int key):根據(jù)鍵獲取值。
- delete(int key):根據(jù)鍵刪除值。
- size():返回數(shù)組中當前存儲的鍵值對的數(shù)量。
- keyAt(int index):根據(jù)索引返回鍵。
- valueAt(int index):根據(jù)索引返回值。
- indexOfKey(int key):返回指定鍵的索引。
- indexOfValue(E value):返回指定值的索引。
SparseArray在處理稀疏數(shù)據(jù)時非常有效,如果數(shù)據(jù)結(jié)構(gòu)不是稀疏的,或者需要存儲的鍵不是整數(shù)類型,那么使用HashMap或其他數(shù)據(jù)結(jié)構(gòu)可能更合適。
例如,在自定義視圖中處理大量子視圖時,可能會使用SparseArray來存儲與每個子視圖ID關聯(lián)的視圖對象。
SparseArray性能
- SparseArray內(nèi)部使用int[] keys數(shù)組維護key,而且keys中元素是按照升序進行排序的,使用Object[] values來維護value。
- SparseArray用于映射integers到object。但不像普通數(shù)組,SparseArray元素間沒有無用的元素。在映射integers到object的過程中,SparseArray采用避免自動裝箱的keys而且它的數(shù)據(jù)結(jié)構(gòu)不依賴于額外的對象來存儲映射關系的實現(xiàn),因此它相比于HashMap占用更少的內(nèi)存。
- 但是SparseArray在查找keys的過程中使用了二分法查找,這種實現(xiàn)不適合大量的數(shù)據(jù)的情況。在添加和刪除時涉及到數(shù)組元素的挪動,因此比HashMap慢。
- 為了優(yōu)化性能,SparseArray對remove()進行了優(yōu)化,在刪除時并沒有立即擠壓數(shù)組的空間,而是標記為DELETE。被標記為DELETE的元素,要么被重復利用,要么在多次remove后,通過一次gc操作,進行數(shù)組空間的擠壓。