SparseArray解析

631 阅读4分钟

SparseArray

Sparse[spɑːrs]

文档介绍

/**
 * <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects,
 * its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient
 * than a
 * <a href="/reference/java/util/HashMap"><code>HashMap</code></a>, because it avoids
 * auto-boxing keys and its data structure doesn't rely on an extra entry object
 * for each mapping.
 *
 * <p>Note that this container keeps its mappings in an array data structure,
 * using a binary search to find keys. The implementation is not intended to be appropriate for
 * data structures
 * that may contain large numbers of items. It is generally slower than a
 * <code>HashMap</code> because lookups require a binary search,
 * and adds and removes require inserting
 * and deleting entries in the array. For containers holding up to hundreds of items,
 * the performance difference is less than 50%.
 *
 * <p>To help with performance, the container includes an optimization when removing
 * keys: instead of compacting its array immediately, it leaves the removed entry marked
 * as deleted. The entry can then be re-used for the same key or compacted later in
 * a single garbage collection of all removed entries. This garbage collection
 * must be performed whenever the array needs to be grown, or when the map size or
 * entry values are retrieved.
 *
 * <p>It is possible to iterate over the items in this container using
 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
 * <code>keyAt(int)</code> with ascending values of the index returns the
 * keys in ascending order. In the case of <code>valueAt(int)</code>, the
 * values corresponding to the keys are returned in ascending order.
 */

SparseArray是谷歌提供的k-v键值对存储类,key固定为int,value为泛型(内部为Object)。虽然

内部主要方法

ContainerHelpers

工具类,二分查找法查找int或者long值,找到返回index,没有找到返回取反后的值(为负数)。

PS:

>>: 	带符号右移
>>>: 	无符号右移
最高位为1表示负数,负数则数值位取反

01111111111111111111111111111111 int maxVal   补:01111111111111111111111111111111
10000000000000000000000000000000 int minVal   补:11111111111111111111111111111111
因为在计算机系统中,数值一律用补码来表示和存储。0的补码属于正数范围,所以int值的范围高区间(正数区间)减1:
-2^31~2^31-1

// 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 取反为负
}

put

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

先用二分查找法查找查找值的index,index大于0则集合中存在,小于0则不存在(见ContainerHelpers)。找到则更新;没找到,则获取需要插入的index位置(返回的负数为插入位置取反),key数组和value数组分别在对应index插入元素。也就是说这里面的元素是通过key的大小进行排序的。其中插入元素的方法是在GrowingArrayUtils.insert内部中调用System.arraycopy,内部真实数组的size在这里进行改变

delete

删除某个键值对,这里的删除并不是真实删除,而是把它的value标记为DELETED,mGarbage标记为true。然后在put、size、keyAt、valueAt、setValueAt、indexForKey、indexOfValue、indexOfValue、indexOfValueByValue、append等方法中触发成员gc方法

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

gc

遍历数组,把未被标记为DELETE的元素放到数组前面,并刷新size大小。(这里的size并不是内存两个数组的size大小,而是有效位数的大小)

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

get

通过key获取元素。根据调用方法获取不到返回默认或者null。

/**
 * Gets the Object mapped from the specified key, or <code>null</code>
 * if no such mapping has been made.
 */
public E get(int key) {
    return get(key, null);
}

/**
 * Gets the Object mapped from the specified key, or the specified Object
 * if no such mapping has been made.
 */
@SuppressWarnings("unchecked")
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];
    }
}

append

对比put,在append的元素大于最大的一个的时候,直接追加在最后,而不是先二分查找再插入。不大于最后一个的时候就调用put。

/**
 * 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++;
}

和HashMap、ArrayMap对比,SparseArray的优缺点:

SparseArray的限制在于键必须是int类型,值必须是Object类型。这样可以避免key自动装箱产生过多的Object。但是这样的话,如果key值相同,那么数据就会被直接覆盖。

SparseArray不能保证保留它们的插入顺序,在迭代的时候应该注意。SparseArray中没有Iterator,SparseArray只实现了Cloneable接口,而没有继承Collection、List或者Map接口。

查找数据的时候使用的是二分法,明显比通过hashcode慢,所以数据越大,查找速度慢的劣势越明显,所以SparseArray适于数据一千以内的场景中。

优点:

  • 避免了基本数据类型的装箱操作
  • 不需要额外的结构体,单个元素的存储成本更低
  • 数据量小的情况下,随机访问的效率更高

缺点:

  • 插入操作需要复制数组,增删效率降低
  • 数据量巨大时,复制数组成本巨大,gc()成本也巨大
  • 数据量巨大时,查询效率也会明显下降

————————————————

参考资料:

​ 优缺点总结:blog.csdn.net/b1480521874…