SparseArray是android.util包中提供的類,用于建立整數對對象的映射,比HashMap性能更佳,因為它避免了自動裝箱并且內部數據結構不依賴額外實體對象。因為使用二分搜索來尋找鍵,所以不適合存儲數量比較大(數百以上)的情況。
//已刪除值通用的標記對象
private static final Object DELETED = new Object();
//是否需要進行gc
private boolean mGarbage = false;
//存儲鍵的數組
private int[] mKeys;
//存儲值的數組
private Object[] mValues;
//存儲大小
private int mSize;
/**
* Creates a new SparseArray containing no mappings.
*/
public SparseArray() {
this(10);
}
/**
* Creates a new SparseArray containing no mappings that will not
* require any additional memory allocation to store the specified
* number of mappings. If you supply an initial capacity of 0, the
* sparse array will be initialized with a light-weight representation
* not requiring any additional array allocations.
*/
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;
}
構造函數。默認容量為10。
/**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
*/
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
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);
}
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
上面是增。順便附上ContainerHelpers類的binarySearch方法的代碼:
// This is Arrays.binarySearch(), but doesn't do any argument validation.
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
}
這是相當標準的二分搜索實現。final int mid = (lo + hi) >>> 1,這里用無符號右移符>>>避免了溢出。當查找不到指定值時,返回的是lo的按位取反值,為負數。因此put方法中可以用返回值是否大于等于0來判斷是否查找成功。
回頭看put方法。若二分搜索成功則直接替換對應值,否則將i按位取反得到非負數,然后看對應值是否已刪除,是則替換對應鍵值,否則先需要gc則gc,再將新鍵值插入數組。GrowingArrayUtils的insert方法中,若數組已滿,則擴容為2倍。
/**
* Puts a key/value pair into the array, optimizing for the case where
* the key is greater than all existing keys in the array.
*/
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
}
mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
mValues = GrowingArrayUtils.append(mValues, mSize, value);
mSize++;
}
增的另一個方法。優化新鍵比現有鍵都大的情況,免去二分搜索,直接插入到末位。
/**
* Alias for {@link #delete(int)}.
*/
public void remove(int key) {
delete(key);
}
/**
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
刪。(同樣的功能硬是給兩個方法入口總覺得有點尷尬。)二分搜索,若查找成功且對應值未被刪除則將該值標為已刪除并記錄待gc。這里的刪除并不是馬上移動數組元素,而是將刪除對象標記,待插入相同鍵時重用或者在之后需要gc時才調整數組。這是考慮了性能優化的方法。
/**
* Gets the Object mapped from the specified key, or <code>null</code>
* if no such mapping has been made.
*/
public E get(int key) {
return get(key, null);
}
/**
* Gets the Object mapped from the specified key, or the specified Object
* if no such mapping has been made.
*/
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
查。使用二分搜索。
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
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;
// Log.e("SparseArray", "gc end with " + mSize);
}
gc方法。遍歷值數組,將標記為刪除的對象置空并前移鍵。
/**
* Returns the number of key-value mappings that this SparseArray
* currently stores.
*/
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
gc前的mSize不是準確的存儲大小,所以獲取size需要先進行gc。