美文网首页
SparseArray源码解析

SparseArray源码解析

作者: RegExp | 来源:发表于2021-04-25 13:58 被阅读0次

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()成本也巨大
  • 数据量巨大时,查询效率也会明显下降

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

参考资料:

  优缺点总结:https://blog.csdn.net/b1480521874/article/details/84983772

相关文章

  • [原创]SparseArray源码解析

    SparseArray源码解析 构造 put GrowingArrayUtils.insert growsize ...

  • HashMap源码解析

    HashMap源码解析 前言 之前写过一篇SparseArray的源码解析,今天我们就对HashMap下手,撸一撸...

  • SparseArray

    参考 Java&Android 基础知识梳理(10) - SparseArray 源码解析 这个类比较简单,解析...

  • 容器类源码解析系列(四)---SparseArray分析(最新版

    容器类源码解析系列(四)---SparseArray分析(最新版) 引言 容器类源码解析系列已经更新到了第四篇,前...

  • SparseArray 源码解析

    使用 Android Studio 作为 IDE 的开发者可能会遇到一个现象,就是在代码中如果声明了 Map ...

  • SparseArray源码解析

    SparseArray是android sdk提供的一个集合类,主要是用来替换是用于替代key为int类型,val...

  • SparseArray源码解析

    在Android中,现在很多时候都会用SparseArray来代替HashMap存放数据,但是有些情况是HashM...

  • SparseArray源码解析

    SparseArray Sparse[spɑːrs] 文档介绍 SparseArray是谷歌提供的k-v键值对存储...

  • ArrayMap源码解析

    上篇文章是SparseArray源码解析,这篇文章分析下ArrayMap的源码。 在移动设备端内存资源很珍贵,Ha...

  • Android SparseArray 源码解析

    是什么东西? sparse 是稀疏的意思,顾名思义,是一个稀疏数组,但实际上,他是一个key 只能为 int 的k...

网友评论

      本文标题:SparseArray源码解析

      本文链接:https://www.haomeiwen.com/subject/ddydrltx.html