Android开发中,经常会遇到选择多个标签或内容进行统一提交处理的情况;通常做法就是用HashMap来保存每个位置的选中状态;了解到Android专门为这种状况提供了SparseArray这个类。从字面理解就是稀疏数组,老规矩,进行一下深入的了解。
HashMap数据结构:
既然Android专门提供了这个类,相比于HashMap应该有很大的优势才对,先回顾一下HashMap的数据结构:
简单说就是一个数组+链表的结构,根据元素的哈希值分配到不同的Bucket里;
SparseArray数据结构:
那SparseArray是什么样的结构呢?看源码:
private int[] mKeys;
private Object[] mValues;
private int mSize;
就是两个数组,看命名就知道 一个放key,一个放value
方法分析:
我们接着往下看它的相关方法实现:
/**
* Appends an element to the end of the array, growing the array if there is no more room.
* @param array The array to which to append the element. This must NOT be null.
* @param currentSize The number of elements in the array. Must be less than or equal to
* array.length.
* @param element The element to append.
* @return the array to which the element was appended. This may be different than the given
* array.
*/
public static <T> T[] append(T[] array, int currentSize, T element) {
assert currentSize <= array.length;
if (currentSize + 1 > array.length) {
@SuppressWarnings("unchecked")
T[] newArray = ArrayUtils.newUnpaddedArray(
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
array = newArray;
}
array[currentSize] = element;
return array;
}
/* 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;
} } }
/**
* 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++;
}
}
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);
}
两个添加方法 : append 和 put 一个删除方法 :delete 一个私有方法 : gc
先来分析一下append方法 :
append 方法直接添加一个entry到SparseArray中:代码逻辑很简单,我们来看一下内部使用了两个其他的方法:
T[] newArray = ArrayUtils.newUnpaddedArray(
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
看代码意思,这里应该是一个数组扩容的操作,我们先看一下
ArrayUtils.newUnpaddedArray<span style="font-family: Arial, Helvetica, sans-serif;">(</span><span style="font-family: Arial, Helvetica, sans-serif;">(Class<T>) array.getClass().getComponentType(), growSize(currentSize));</span>
看看ArrayUtils是怎么实现这个方法的 :
@SuppressWarnings("unchecked")
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
}
调用了VMRuntime,我们接着看这个类:
http://androidxref.com/5.0.0_r2/xref/libcore/libart/src/main/java/dalvik/system/VMRuntime.java#260
/**
* Returns an array of at least minLength, but potentially larger. The increased size comes from
* avoiding any padding after the array. The amount of padding varies depending on the
* componentType and the memory allocator implementation.
*/
public native Object newUnpaddedArray(Class<?> componentType, int minLength);
native方法 ,看注释,返回一个新的数组,可能比设置的minLength大;(英文不好,没看太明白。。。。)
好吧,先不深究这个,毕竟我们不是来追它底层怎么实现的;
看下另外一个方法:
System.arraycopy(array, 0, newArray, 0, currentSize);
几个参数什么意思呢?
自己查吧 。。。就是一个复制数组的方法
下一个方法,put方法:
这里也有很关键的两个操作:
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
我们来看下这两个方法是怎么实现的:
ContainerHelpers:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/ContainerHelpers.java
// 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
}
GrowingArrayUtils:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/util/GrowingArrayUtils.java
/**
* Inserts an element into the array at the specified index, growing the array if there is no
* more room.
*
* @param array The array to which to append the element. Must NOT be null.
* @param currentSize The number of elements in the array. Must be less than or equal to
* array.length.
* @param element The element to insert.
* @return the array to which the element was appended. This may be different than the given
* array.
*/
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
assert currentSize <= array.length;
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
@SuppressWarnings("unchecked")
T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
System.arraycopy(array, index, newArray, index + 1, array.length - index);
return newArray;
}
/**
* Appends an element to the end of the array, growing the array if there is no more room.
* @param array The array to which to append the element. This must NOT be null.
* @param currentSize The number of elements in the array. Must be less than or equal to
* array.length.
* @param element The element to append.
* @return the array to which the element was appended. This may be different than the given
* array.
*/
public static <T> T[] append(T[] array, int currentSize, T element) {
assert currentSize <= array.length;
if (currentSize + 1 > array.length) {
@SuppressWarnings("unchecked")
T[] newArray = ArrayUtils.newUnpaddedArray(
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
array = newArray;
}
array[currentSize] = element;
return array;
}
好吧 ,看到有个append方法竟然 ,又回头翻了一下SparseArray的源码,果然也有调用
/**
* 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++;
}
源码都有了,想必大家都能看明白了。。。不错,二分法
关键就是这个
binarySearch
二分查找结束后返回的位置也很重要 ,多看几次就明白了,哈哈
最后说一下delete方法和gc方法 :
delete方法中并没有直接把元素移除掉,而是将值改为DELETE这个固定值 ,并且将flag置为true;
这样就保证了不用每次remove的时候就调用一次gc进行压缩(怎么压缩的看gc实现)
结尾了哈 :
通过查看实现方案发现,其实SparseArray也并没有多神奇,数组结构查找快,但是增删的时候也是慢很多;相比于hashmap来说各有千秋吧。不过针对于我最开始描述的需求使用,简直再适合不过呀 。。数据量大且操作多的时候慎用哈 。。