SparseArray详解

Android开发中,经常会遇到选择多个标签或内容进行统一提交处理的情况;通常做法就是用HashMap来保存每个位置的选中状态;了解到Android专门为这种状况提供了SparseArray这个类。从字面理解就是稀疏数组,老规矩,进行一下深入的了解。


HashMap数据结构:

既然Android专门提供了这个类,相比于HashMap应该有很大的优势才对,先回顾一下HashMap的数据结构:

简单说就是一个数组+链表的结构,根据元素的哈希值分配到不同的Bucket里;

SparseArray数据结构:

那SparseArray是什么样的结构呢?看源码:
private int[] mKeys;
private Object[] mValues;
private int mSize;
就是两个数组,看命名就知道 一个放key,一个放value

方法分析:


我们接着往下看它的相关方法实现:
/**
     * Appends an element to the end of the array, growing the array if there is no more room.
     * @param array The array to which to append the element. This must NOT be null.
     * @param currentSize The number of elements in the array. Must be less than or equal to
     *                    array.length.
     * @param element The element to append.
     * @return the array to which the element was appended. This may be different than the given
     *         array.
     */
    public static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;
        if (currentSize + 1 > array.length) {
            @SuppressWarnings("unchecked")
            T[] newArray = ArrayUtils.newUnpaddedArray(
                    (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }

/* Removes the mapping from the specified key, if there was any. */ 
    public void delete(int key) { 
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 
        if (i >= 0) { 
           if (mValues[i] != DELETED) {
               mValues[i] = DELETED; mGarbage = true; 
           } } }



/**
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * was one.
     */
    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++;
        }
    }
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);
    }

列出了4个重要的方法,想看其他的同学请翻墙看源码:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/SparseArray.java
两个添加方法 : append 和 put    一个删除方法 :delete   一个私有方法 : gc

先来分析一下append方法 :
append 方法直接添加一个entry到SparseArray中:代码逻辑很简单,我们来看一下内部使用了两个其他的方法:
T[] newArray = ArrayUtils.newUnpaddedArray(
                    (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
看代码意思,这里应该是一个数组扩容的操作,我们先看一下
ArrayUtils.newUnpaddedArray<span style="font-family: Arial, Helvetica, sans-serif;">(</span><span style="font-family: Arial, Helvetica, sans-serif;">(Class<T>) array.getClass().getComponentType(), growSize(currentSize));</span>
看看ArrayUtils是怎么实现这个方法的 :

@SuppressWarnings("unchecked")
    public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
        return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
    }
调用了VMRuntime,我们接着看这个类:http://androidxref.com/5.0.0_r2/xref/libcore/libart/src/main/java/dalvik/system/VMRuntime.java#260
/**
 * Returns an array of at least minLength, but potentially larger. The increased size comes from
 * avoiding any padding after the array. The amount of padding varies depending on the
 * componentType and the memory allocator implementation.
*/
public native Object newUnpaddedArray(Class<?> componentType, int minLength);
native方法 ,看注释,返回一个新的数组,可能比设置的minLength大;(英文不好,没看太明白。。。。)
好吧,先不深究这个,毕竟我们不是来追它底层怎么实现的;
看下另外一个方法:
System.arraycopy(array, 0, newArray, 0, currentSize);
几个参数什么意思呢? 
自己查吧 。。。就是一个复制数组的方法


下一个方法,put方法:
这里也有很关键的两个操作:
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
我们来看下这两个方法是怎么实现的:
// This is Arrays.binarySearch(), but doesn't do any argument validation.
    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
    }


 /**
     * Inserts an element into the array at the specified index, growing the array if there is no
     * more room.
     *
     * @param array The array to which to append the element. Must NOT be null.
     * @param currentSize The number of elements in the array. Must be less than or equal to
     *                    array.length.
     * @param element The element to insert.
     * @return the array to which the element was appended. This may be different than the given
     *         array.
     */
    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;
    }
/**
     * Appends an element to the end of the array, growing the array if there is no more room.
     * @param array The array to which to append the element. This must NOT be null.
     * @param currentSize The number of elements in the array. Must be less than or equal to
     *                    array.length.
     * @param element The element to append.
     * @return the array to which the element was appended. This may be different than the given
     *         array.
     */
    public static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;
        if (currentSize + 1 > array.length) {
            @SuppressWarnings("unchecked")
            T[] newArray = ArrayUtils.newUnpaddedArray(
                    (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }

好吧 ,看到有个append方法竟然 ,又回头翻了一下SparseArray的源码,果然也有调用
 /**
     * Puts a key/value pair into the array, optimizing for the case where
     * the key is greater than all existing keys in the array.
     */
    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++;
    }
源码都有了,想必大家都能看明白了。。。不错,二分法 
关键就是这个 
binarySearch
二分查找结束后返回的位置也很重要 ,多看几次就明白了,哈哈 

最后说一下delete方法和gc方法 :
delete方法中并没有直接把元素移除掉,而是将值改为DELETE这个固定值 ,并且将flag置为true;
这样就保证了不用每次remove的时候就调用一次gc进行压缩(怎么压缩的看gc实现)


结尾了哈 :
通过查看实现方案发现,其实SparseArray也并没有多神奇,数组结构查找快,但是增删的时候也是慢很多;相比于hashmap来说各有千秋吧。不过针对于我最开始描述的需求使用,简直再适合不过呀 。。数据量大且操作多的时候慎用哈 。。


展开阅读全文

没有更多推荐了,返回首页