SparseArray

Posted by OOFTF Blog on July 12, 2021

源码分析

数据结构

构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public SparseArray() {
    // 默认容量为 initialCapacity
    this(10);
}
public SparseArray(int initialCapacity) {
    if (initialCapacity == 0) {
        mKeys = EmptyArray.INT;
        mValues = EmptyArray.OBJECT;
    } else {
        // 当 initialCapacity 为偶数时创建大小为 initialCapacity+1 大小的数组,每个值都为 null
        // 当 initialCapacity 为奇数时创建大小为 initialCapacity 大小的数组,每个值都为 null
        mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
        // 创建大小为 mValues.lenght 的数组,每个值都为 0
        mKeys = new int[mValues.length];
    }
    mSize = 0;
}

put 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void put(int key, E value) {
    // 通过 二分查找 找到 key,如果 key 已经存在返回 key 的位置,如果 key 不存在返回应该插入的位置取反的值,这个值是一个负数 
    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;
        }
        //mGarbage 表示是否含有 Value 为 DELETED 的数据,如果通过 deleted 或者 removeAt 方法清除数据 mGarbage 就会置为 true
        if (mGarbage && mSize >= mKeys.length) {
            // gc 方法会删除 value 为 DELETED 的数据,并把有效数据移动到数组前面部分
            gc();
            // GC 后重新获取插入位置
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }
        // 如果 mSize < mKeys.length 插入到源数组,如果 mSize= mKeys.length 新建一个数组
        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        mSize++;
    }
}
SparseArray.gc()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将有效数据,向前移动,剔除 value 为 DELETED 的数据
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;
}

GrowingArrayUtils.insert 插入,扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
    }
    //当前数组长度不够用,需要扩容
    //  ArrayUtils.newUnpaddedArray 方法,如果传入大小为偶数会+1 变为奇数
    // 扩容大小:如果原来大小 小于等于 4 扩容为 9,如果大于4 扩容为原来大小的两倍+1
    @SuppressWarnings("unchecked")
    T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
            growSize(currentSize));
    // copy 前半部分        
    System.arraycopy(array, 0, newArray, 0, index);
    // 插入数值
    newArray[index] = element;
    // copy 后半部分
    System.arraycopy(array, index, newArray, index + 1, array.length - index);
    // 返回新数组
    return newArray;
}
public static int growSize(int currentSize) {
    return currentSize <= 4 ? 8 : currentSize * 2;
}

get 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public E get(int key) {
    return get(key, null);
}
public E get(int key, E valueIfKeyNotFound) {
    //通过二分查找,找到key 所在位置
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i < 0 || mValues[i] == DELETED) {
        // 如果没有找到 返回 默认值
        return valueIfKeyNotFound;
    } else {
        // 如果找到返回对应的 value
        return (E) mValues[i];
    }
}

delete 和 remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void remove(int key) {
    delete(key);
}
public void delete(int key) {
    // 二分查找对应的位置
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i >= 0) {
        // 如果存在,将其值置为 DELETED
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED;
            // 是否含有垃圾数据,置为 true,用于gc() 方法判断
            mGarbage = true;
        }
    }
}

// 逻辑和 delete 类似
public void removeAt(int index) {
    if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    if (mValues[index] != DELETED) {
        mValues[index] = DELETED;
        mGarbage = true;
    }
}

####

总结

  • 数组大小会补充为奇数
  • 默认容量为 10 ,经过补充会变为 11
  • 底层数据结构为两个相同大小的数组
  • key 和 value,保存在两个数组下相同角标的位置上
  • key 数组按照 key 大小顺序排列
  • 查找 key 的方式,是二分查找,时间复杂度为 O(log2n)
  • 删除元素并不会从数组中直接移除,而是将其 value 值,置为 DELETED
  • 扩容大小:如果原来大小 小于等于 4 扩容为 9,如果大于4 扩容为原来大小的两倍+1

SparseArray 相对于 HashMap 有什么优缺点

优点:

  • 避免了对key的自动装箱(int转为Integer类型)
  • 使用二分查找通常情况下比 HashMap hashcode 求数组下标+遍历链表 的方式效率更高
  • 删除方法更简单,只是简单的将 value 标记为删除 DELETED

缺点:

  • 由于 SparseArray 添加、查找、删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显