SparseArray详解

来源:互联网 发布:淘宝软件有限公司 股权 编辑:程序博客网 时间:2024/05/18 01:49

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是怎么实现这个方法的 :
源码地址: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/util/ArrayUtils.java

@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);
我们来看下这两个方法是怎么实现的:
ContainerHelpers:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/ContainerHelpers.java
// 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    }


GrowingArrayUtils:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/util/GrowingArrayUtils.java
 /**     * 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来说各有千秋吧。不过针对于我最开始描述的需求使用,简直再适合不过呀 。。数据量大且操作多的时候慎用哈 。。


0 0
原创粉丝点击