SparseArray数据结构:
private int[] mKeys; // int 数组 保存 key
private Object[] mValues; // Object 数组 存value
private int mSize; // SparseArray 的长度
SparseArray sparseArray = new SparseArray<>();// 还有同胞兄弟 SparseBooleanArray,LongSparseArray
sparseArray .put(1,1);
public void put(int key, E value) {
// 由于采用二分法查找键的位置,所以没有的话返回小于0的数值,而不是返回-1,
// 这点要注意,返回的负数其实是表示它在哪个位置就找不到了,
// 如果你存了5个,查找的键大于5个值的话,返回就是-6:
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
// 查找到,修改数组的值
mValues[i] = value;
} else {
//没找到 对-1 再取反 i= 0;
i = ~i;
// DELETED 删除标志 相当于 HashMap 赋值 == null
// mSize SparseArray的长度
// i 索引值 小于 数组长度 且对应的值需要被 移除
// delete方法中并没有直接把元素移除掉,而是将值改为DELETE这个固定值 ,并且将flag置为true;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();// 调用gc 重排序数组
// Search again because indices may have changed.
// 再次搜索,因为索引可能已经改变。gc中会做些改变
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//将key 和value 将插入数组
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
- 总体过程 就是先二分查找 keys数组中是否有这个值,有直接覆盖掉values[],其余不变,查找不到将key 和 value 插入到数组中
// 二分查找 基础不浪费时间
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
}
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];
// 1 2 3 X 5 6,当出现要移除的数时 X,i > o 循环结束后 1 2 3 4 5 null
// 和HashMap 一样,移除 制null
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;// 重新更新数组的长度,
}
gc方法就是把 移除的数据 key和value都被数组中后一个 正常的数据 key 和value覆盖,最后移除的数据被移动到数组最后,并被置 null。
关于 System.arraycopy 参数一 表示源数组,参数而二 表示源数组要复制的起始位置,参数三 表示目标数组,参数四目的数组放置的起始位置,length表示要复制的长度。
// GrowingArrayUtils.insert 可以参考ArrayList 扩容
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;
}
- SparseArray应用场景:
- 数据量不大,最好在千级以内
- key 必须 为int类型,这中情况下可以用SparseArray代替HashMap
我有看到好多帖子 .插入性能时间对比 倒序插入效率很差但是都没有说明为什么差,难受
2. SparseArray 较HashMap 性能优化
- SparseArray只能存储 key 是int 类型,并且不需要自动装箱 Integer;HashMap 全能,需要自动装箱成包装类
- SparseArray 底层是两个数组一个存key 一个存value 而不是HashMap中的稀疏存储;HashMap 底层时 数组加链表(JDK1.8 红黑树)。什么是稀疏数组(Sparse array)?
- 查找时 SparseArray 二分查找 时间复杂度O()=O(logn);HashMap 红黑树的时间复杂度O(N)
ArrayMap每存储一条信息,需要保存一个hash值,一个key值,一个value值,当出现哈希冲突时,会在index的相邻位置插入;也是二分查找,两个数组
借鉴:ArrayMap完全剖析