SparseArray源码浅析

Posted zhujm320

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SparseArray源码浅析相关的知识,希望对你有一定的参考价值。

介绍

        SparseArray是android特有的轻量级数据结构,类似于hashmap以键值对的方式进行存储,但是key类型为int,key-value分别采用数组进行存储,采用二分法进行查询,具有很高的查询效率(时间复杂度O(logn)),但是插入和删除效率比较低(需要移动数组), 不适合大量数据的情况。

图示

        从上图中可以看出,key-value分别存储在Key数据和Value数组中,它们有者共同的索引。插入,删除,查询操作,遍历key数组通过二分查找法,找到key对应的index,那么value的位置在value数组下标为index地方。

属性介绍

//数组中元素删除标记(假删除)
 private static final Object DELETED = new Object();
 //是否进行回收的标记, 删除元素后, mGarbage=true
 private boolean mGarbage = false;
//保存key的数组
@UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
private int[] mKeys;
//保存value的数组
@UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E)
private Object[] mValues;
//容器大小
@UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
private int mSize;

方法介绍

构造方法

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;
}

构造函数,初始化key和value数组,默认构造函数初始化key和value数组大小为10

克隆操作

 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) {
            /* ignore */
        }
        return clone;
}

是否包含特定的key

public boolean contains(int key) {
        return indexOfKey(key) >= 0;
    }
    /*
    * 如果在该操作之前有进行删除操作,就进行一次垃圾回收
    */
    public int indexOfKey(int key) {
        if (mGarbage) {
            gc();
        }
        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }
    /*
    * 通过二分法找到key对应的索引
    */
    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
    }

通过value查找index

public int indexOfValueByValue(E value) {
        if (mGarbage) {
            gc();
        }

        for (int i = 0; i < mSize; i++) {
            if (value == null) {
                if (mValues[i] == null) {
                    return i;
                }
            } else {
                if (value.equals(mValues[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

清空所有的元素

public void clear() {
        int n = mSize;
        Object[] values = mValues;

        for (int i = 0; i < n; i++) {
            values[i] = null;
        }

        mSize = 0;
        mGarbage = false;
    }

追加元素

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++;
    }
    /*
    * 插入元素
    */
    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++;
        }
    }

查询

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];
        }
    }

根据key查询value,基本思想也是遍历key数组找到key对应的index,然后从value数组中取出index对应的值

删除

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

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }
    /*
    * 删除并返回value
    */
     public E removeReturnOld(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                final E old = (E) mValues[i];
                mValues[i] = DELETED;
                mGarbage = true;
                return old;
            }
        }
        return null;
    }
    /*
    * 根据索引进行删除
    */
    public void removeAt(int index) {
        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
            // Check if exception should be thrown outside of the critical path.
            throw new ArrayIndexOutOfBoundsException(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);
        }
    }

根据key删除对应的元素,注意这里是假删除,只是将需要删除的位置设置删除标记,同时设置垃圾回收标记。假删除的好处是进行了内存复用,避免重复申请内存。将垃圾回收,延迟到一下操作(如: 插入,计算大小)

垃圾回收

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);
    }

将value数组中值为DELETED的元素全部设置为null,以便进行垃圾回收

元素个数

public int size() {
        if (mGarbage) {
            gc();
        }

        return mSize;
    }

根据索引查询key

public int keyAt(int index) {
        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
            // Check if exception should be thrown outside of the critical path.
            throw new ArrayIndexOutOfBoundsException(index);
        }
        if (mGarbage) {
            gc();
        }

        return mKeys[index];
    }

根据索引查询value

public E valueAt(int index) {
        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
            // Check if exception should be thrown outside of the critical path.
            throw new ArrayIndexOutOfBoundsException(index);
        }
        if (mGarbage) {
            gc();
        }

        return (E) mValues[index];
    }

根据索引设置value

public void setValueAt(int index, E value) {
        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
            // Check if exception should be thrown outside of the critical path.
            throw new ArrayIndexOutOfBoundsException(index);
        }
        if (mGarbage) {
            gc();
        }

        mValues[index] = value;
    }

根据key查询索引

 public int indexOfKey(int key) {
        if (mGarbage) {
            gc();
        }

        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }

根据value查询索引

public int indexOfValue(E value) {
        if (mGarbage) {
            gc();
        }
        for (int i = 0; i < mSize; i++) {
            if (mValues[i] == value) {
                return i;
            }
        }
        return -1;
    }
    
     public int indexOfValueByValue(E value) {
        if (mGarbage) {
            gc();
        }

        for (int i = 0; i < mSize; i++) {
            if (value == null) {
                if (mValues[i] == null) {
                    return i;
                }
            } else {
                if (value.equals(mValues[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

toString实现

public String toString() {
        if (size() <= 0) {
            return "{}";
        }

        StringBuilder buffer = new StringBuilder(mSize * 28);
        buffer.append('{');
        for (int i=0; i<mSize; i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            int key = keyAt(i);
            buffer.append(key);
            buffer.append('=');
            Object value = valueAt(i);
            if (value != this) {
                buffer.append(value);
            } else {
                buffer.append("(this Map)");
            }
        }
        buffer.append('}');
        return buffer.toString();
    }

总结:

        SparseArray作为轻量级key-value数据结构,适用于key为int的场景,避免了自动装箱的操作,在该场景下可以替代HashMap,进行内存优化。

以上是关于SparseArray源码浅析的主要内容,如果未能解决你的问题,请参考以下文章

SparseArray源码浅析

SparseArray源码浅析

SparseArray 源码分析

SparseArray 源码分析

Android SparseArray源码分析

HashMap,ArrayMap,SparseArray源码分析及性能对比