1、简述
- 我们都知道 HashMap,它属于 java.util 包下,但是很多人可能对 SparseArray 并不是很熟悉,通俗来说 SparseArray 属于 android.util 包下,是用于 Android 平台某些情况替换 HashMap 的数据结构。
- 更具体的来说,是用于替代 key 为 int 类型,value 为 Object 类型的 HashMap。和 ArrayMap 类似,它的实现相比于 HashMap 更加节省空间,而且由于 key 指定为 int 类型,也可以节省 int 到 Integer 的装箱拆箱操作带来的性能消耗。
2、归纳
- 实现了 Cloneable 接口,并重写了 clone() 方法,所以支持复制(拷贝)。
- 底层基于两个一维数组来实现的,其 key 为 int 类型,且 key 是按照从小到大升序存储的;value 为 Object 类型,且允许 value 为 null。
- 扩容策略,初始化容量为10,如果当前容量小于等于4,则返回8,负责返回当前容量的2倍。
- SparseArray 的扩容方式是直接创建一个新的数组,并将数据拷贝到新数组中。
- 增、删和查操作都用到了二分查找【时间复杂度:O(log n)】,改操作【时间复杂度:O(1)】。
- SparseArray 的操作单线安全,多线程不安全。
3、分析
3.1、接口
- 在分析 SparseArray 源码之前,我们先来看看其实现的唯一接口 Cloneable,Cloneable 其实就是一个标记接口,实现这个接口后,然后重写 Object#clone() 方法,可以让其支持复制(拷贝)功能。
public interface Cloneable { }
@Override
@SuppressWarnings("unchecked")
public SparseArray<E> clone() {
SparseArray<E> clone = null;
try {
clone = (SparseArray<E>) super.clone();
clone.mKeys = mKeys.clone();
clone.mValues = mValues.clone();
} catch (CloneNotSupportedException cnse) {
}
return clone;
}
3.2、成员变量
- 在 SparseArray 的源码中,其成员变量并不多,所以在此把它们都一一列出。
public class SparseArray<E> implements Cloneable {
...
private static final Object DELETED = new Object();
private boolean mGarbage = false;
@UnsupportedAppUsage(maxTargetSdk = 28)
private int[] mKeys;
@UnsupportedAppUsage(maxTargetSdk = 28)
private Object[] mValues;
@UnsupportedAppUsage(maxTargetSdk = 28)
private int mSize;
...
}
3.3、构造函数
- 构造函数是一个类最常见的,同样也是最常被使用到的,接着我们分析 SparseArray 的两个不同的构造函数。
public class SparseArray<E> implements Cloneable {
...
public SparseArray() {
this(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;
}
...
}
3.4、增
- SparseArray 的增操作为 put(int key, E value),下面我们来分析这种实现。
public class SparseArray<E> implements Cloneable {
...
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();
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
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;
}
...
}
- ContainerHelpers.binarySearch() 方法:
class ContainerHelpers {
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;
}
}
return ~lo;
}
...
}
- GrowingArrayUtils.insert() 方法:
public final class GrowingArrayUtils {
...
public static int[] insert(int[] array, int currentSize, int index, int element) {
assert currentSize <= array.length;
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
int[] newArray = new int[growSize(currentSize)];
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
System.arraycopy(array, index, newArray, index + 1, array.length - index);
return newArray;
}
public static int growSize(int currentSize) {
return currentSize <= 4 ? 8 : currentSize * 2;
}
...
}
- 由上述源码解析可知:
- 二分查找:若未找到返回下标时,对待插入的位置下标取反并返回,这样可以根据返回的正负可以判断是否找到的 index。对负的 index 取反,即可得到应该插入的位置下标。
- 扩容:如果当前容量小于等于4,则返回8,负责返回当前容量的2倍。注意,其扩容操作依然是用数组的复制、覆盖完成(System.arraycopy())。
3.5、删
public class SparseArray<E> implements Cloneable {
...
public void remove(int key) {
delete(key);
}
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
mGarbage = true;
}
}
public void removeAtRange(int index, int size) {
final int end = Math.min(mSize, index + size);
for (int i = index; i < end; i++) {
removeAt(i);
}
}
...
}
3.6、改
public class SparseArray<E> implements Cloneable {
...
public void setValueAt(int index, E value) {
if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
throw new ArrayIndexOutOfBoundsException(index);
}
if (mGarbage) {
gc();
}
mValues[index] = value;
}
...
}
3.7、查
public class SparseArray<E> implements Cloneable {
...
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);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
public int keyAt(int index) {
if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
throw new ArrayIndexOutOfBoundsException(index);
}
if (mGarbage) {
gc();
}
return mKeys[index];
}
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
public int indexOfKey(int key) {
if (mGarbage) {
gc();
}
return ContainerHelpers.binarySearch(mKeys, mSize, key);
}
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++)
if (mValues[i] == value)
return i;
return -1;
}
...
}
4、总结
- 总体来说 SparseAraay 的源码相对比较简单,相对于 HashMap 而言就是“时间换空间”的一个概念,所以1000以内元素个数更推荐使用 SparseArray 集合。
- android.util 包下,还提供了三个类似思想的集合:
- (1)SparseIntArray,value 为 int 类型;
- (2)SparseLongArray,value 为 long 类型;
- (3)SparseBooleanArray,value 为 boolean 类型;
- 这三者与 SparseArray 的唯一区别在于 value 的类型,SparseArray 的 value 可以为任意类型,而上述三者是拆箱后的基本类型。