SparseArray 解析

281 阅读3分钟

序言

SparseArray 避免了key的自动装箱,是最轻量级的存储key为int的map

变量

private boolean mGarbage = false;  //当出现过删除操作,会将此标识位置为true
private int[] mKeys;      //存放key的数组
private Object[] mValues; //存放value的数组
private int mSize;        //对象个数

put

public void put(int key, E value) {
    
    //通过二分法寻找在 mKeys 中 是否有 key
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    
    if (i >= 0) {
    
       // 如果存在key,直接进行值的替换
        mValues[i] = value;
    } else {
        i = ~i;
        
        //如果查找到的位置,小于对象个数,并且当前位置以及被删除了,则直接赋值
        if (i < mSize && mValues[i] == DELETED) {
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        
        //如果之前发送过删除操作 (mGarbage = true) 并且对象个数超过了key数组的长度,
        //证明此时有更多的辣鸡数据,所以进行一次 ‘gc’
        if (mGarbage && mSize >= mKeys.length) {
            gc();
        
            // Search again because indices may have changed.
            
            //整理完数据后,重新计算i的位置
            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];
    
        //val 标记不为DELETED的数据,会重新将数据锁紧,不留出空隙存放null值
        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);
}
    
    
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;
}

put流程:

  1. 二分法查找key的位置
  2. 有值直接覆盖
  3. 如果此时没有对应的key,则将标记为 DELETED 的数据,进行覆盖,覆盖 key 与 vaule
  4. 如果需要gc,先进行gc,然后通过 System.arraycopy 进行插入数据处理

get

public E get(int key) {
    return get(key, null);
}
    
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];
    }
}

判断是否被删除,直接通过数组返回值。

delete

public void delete(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}

将values数组的值标记为DELETED,在put时进行gc操作,进行清除

总结

SparseArray 相对于 ArrayMap 来说

  1. 由于key值只能是int,减少了自动装箱的消耗。
  2. 标记 DELETED ,再重新对数组进行赋值,没有经历数组拷贝。
  3. 没有数组的容量减少操作,数组只能进行扩容操作。

所以在数据量少,且key值为int时,SparseArray的内存消耗更少。由于其二分查找法的缘故,与 ArrayMap 一样,数据量尽量保持1000以下。