SparseArray

参考 Java&Android 基础知识梳理(10) - SparseArray 源码解析

这个类比较简单,解析源码的思路为:先看注释,了解设计初衷,然后从我们平时使用的接口入手查看实现,最后再来验证下设计初衷

  1. 类的注释讲解:
/**
 * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
 * there can be gaps in the indices.  It is intended to be more memory efficient
 * than using a HashMap to map Integers to Objects, both because it avoids
 * auto-boxing keys and its data structure doesn't rely on an extra entry object
 * for each mapping.
 *
 * 

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 traditional * HashMap, since 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 not significant, less than 50%.

* *

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 step of all removed entries. This garbage collection will * need to be performed at any time the array needs to be grown or the the map size or * entry values are retrieved.

* *

It is possible to iterate over the items in this container using * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using * keyAt(int) with ascending values of the index will return the * keys in ascending order, or the values corresponding to the keys in ascending * order in the case of valueAt(int).

*/
  • spaseArray是一个int到object的映射, 设计初衷是在某些情形下比hashmap更有效率,以替代hashmap; 效率体现在:无需自动装包(keys); 映射很直接,不像hashmap一样依赖其他的存储结构(比如链表);
  • keys直接存在在数组中,二分法查找(因此需保证有序),不适合keys较多的情况,为什么呢:二分法查找的时间复杂度问题+增删数据对数组结构不友好,效率较低;
  • sparsearray为了提高效率做了个优化:删除数据时,把对应的value对象设置为deleted对象,不会真正删除,以备后续复用; 当然,在数组增加等一些操作时垃圾回收期(gc)会触发工作;
  • 支持遍历

从这里我们了解到:

  • keys是一个int数组,values是对象数组,方便!
  • keys直接存储key值,二分法查找,因此它是有序的
  • values 删除并不是真正的删除,只有gc后才会删除,但删除只是删除values,keys并不受影响(因为无所谓)
  • 使用场景:数据量不大的情况下hashmap更有效率,而数据量大了后为什么不行了呢? 面试要点,自己看注释~~~
  1. 我们常用的接口
    我们无非用的是增删查改,开始之前看下存储用的常量吧

private static final Object DELETED = new Object();//标记value已删除,方便复用
private boolean mGarbage = false;//是否要gc
private int[] mKeys;// key的存储
private Object[] mValues; //value的存储
private int mSize; //实际value中有效的值大小~~

二分法实现:这里查看

 public void put(int key, E value) {
       //二分法找key:实现见上面
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {    //找到
            mValues[i] = value;
        } else {       
            i = ~i;
             //未找到,尝试下可否复用未删除的value空间
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }
           // gc  注意:mSize >= mKeys.length
            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++;
        }
    }

  • 比较简单,设置deleted标记, 后续会考虑复用或gc
    public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }
    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];
        }
    }
   public void setValueAt(int index, E value) {
       if (mGarbage) {
           gc();
       }

       mValues[index] = value;
   }

其实原理都不难理解,关键是gc是什么时机触发的,怎么进行的,插入数据是怎么进行的?下面重点看下

  • gc
    触发条件:

在增加数据时判断条件,符合则触发
if (mGarbage && mSize >= mKeys.length)

在 查询key下标,value下标,设置值,求size()时判断条件
if (mGarbage)

怎么进行:

设置o=0,来重新计算有效数值
过滤条件:!=deleted的且o!=i,则赋值覆盖
o的值就是size(),表示values有效的值,注意values无需的值会置为null,但keys不会
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);
    }
  • 数组怎么插入一个值的
    见这里
  1. 现在重读下类的注释,很多问题都有答案了
    sparseArray很轻便,需要在一定的情形下使用可大大提高效率!

你可能感兴趣的:(SparseArray)