Android内存优化-方式九:使用优化过的集合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yaoming168/article/details/88946938

轻量级的int-object键值对——SparseArray

SparseArray是Android framework中提供的轻量级的键值对数据结构,我们知道空间和效率从来都是相悖的,SparseArray的实现正是以时间来换取空间效率,适合小规模数据的存储。
下面来了解下SparseArray的特点,使用,并分析部分源码。
SparseArray特点
SparseArray以键值对的形式保存数据,key是int类型,并且是唯一的不允许重复的key,而value可以是任何object。
SparseArray是轻量级的,使用2个数组分别保存key和value,并通过数组下标来对应关联。key数组是保持有序的。
优点
相比HashMap更加节省内存空间,数据存储只依赖key和value2个数组,数组空间是可复用的,数据的存储密度较高。
因为key数组是有序的,通过key获取value相对高效。
缺点:
key数组是保持有序的,在插入和查找时,通过二分法确定位置key的下标,比较耗时;
插入删除等操作可能会移动数组数据。
综合来说,SparseArray适用于小数据量的键值对场景。数据量达到几百时,效率优势和HashMap相比已不明显。
使用SparseArray
插入
1.SparseArray初始化时需要指定存储数据的具体类型
2.使用put方法,插入key和对应的value
SparseArray strArray = new SparseArray<>();
strArray.put(3, “DDDD”);
strArray.put(1, “AAAA”);
strArray.put(4, “CCCC”);
strArray.put(2, “BBBB”);
SparseArray没有实现Iterable,只能通过手动循环遍历:
for(int i = 0;i<strArray.size();i++){
String value = strArray.valueAt(i);
//do sth
}
插入的4条数据,打印是根据key值从小到大的顺序输出的,这也印证了在执行插入之后,key数组是保持有序的:
value at 0:AAAAvalue at 1:BBBBvalue at 2:DDDDvalue at 3:CCCC
删除
删除元素的方法有2个:

  1. removeAt,删除指定下标的value。
  2. delete通过key删除对应的value。
    从以上插入的4个数据中删除其中2个:
    strArray.remove(3);//删除key为3的值,即DDDD
    strArray.removeAt(0);//删除数组中的第一个值,即AAAA
    结果只剩下BBBB和CCCC:
    value at 0:BBBBvalue at 1:CCCC
    通过key获取元素
    通过get方法传入key来获取对应的value:
    Log.i(TAG, “get 1:” +strArray.get(1));
    Log.i(TAG, “get 4:” +strArray.get(4));
    输出:
    get 1:null //key=1的值为AAAA,已经被删除,因此返回nullget 4:CCCC
    查找
    指定key或者value来查找其下标值:
  3. indexOfKey二分法从mKeys数组中查找key的下标
  4. indexOfValue 遍历查找mValues数组中value元素的下标。
    Log.i(TAG, “index of key 4:” +strArray.indexOfKey(4));
    Log.i(TAG, “index of value BBBB:” +strArray.indexOfValue(“BBBB”));
    输出:
    key=4的值,数组下标为1,
    BBBB在value数组中下标为0
    index of key 4:1index of value BBBB:0
    源码分析
    SparseArray所实现的Cloneable接口,其实是一个空接口,并没有实现什么特性。
    public class SparseArray implements Cloneable
    mKeys和mValues数组
    key和Object之间通过数组下标来对应,整形数组mKeys用于保存键,而mValues保存Object实例。
    private int[] mKeys; //键
    private Object[] mValues; //值
    这2个数组会自动扩容,但从数组移除某个对象后,不会进行减容,数组会重新组织将空位转移到数组末端,留作后面重用。
    删除
    因为删除的操作有点特殊,先分析这部分。
    无论是removeAt还是 delete方法,都不会清除key数组中的值,只会将对应的value标记为DELETED,并将mGarbage值置成true,等待插入,查找下标,获取元素等操作时重新组织数组内容。
    这相当于挖了个坑,后面还需要填平。
    public void removeAt(int index) {
    if (mValues[index] != DELETED) {
    mValues[index] = DELETED;
    mGarbage = true;
    }
    }
    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方法
    这个gc方法并不是java的内存回收方法,它负责将被删除的“坑”填平,填平的方式是使用后面的元素覆盖这个坑,将空位移到数组后端。
    private void gc() {
    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;
    }
    插入操作
    插入操作有可能使数组扩容。
    public void put(int key, E value) {
    //二分查找key在mKeys数组中的下标,如没有搜索到即返回低点index的取反值(负数)
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i >= 0) { //找到对应的key,那么直接将value放入到mValues数组
    mValues[i] = value;
    } else { //key不存在
    i = ~i; //将负数的index再取反,就是插入新元素的位置
    if (i < mSize && mValues[i] == DELETED) { //如果该位置的value刚好被删除,直接替换这个value即可
    mKeys[i] = key;
    mValues[i] = value;
    return;
    }
    if (mGarbage && mSize >= mKeys.length) {
    gc(); //清理被删除的值,重新整理key和value数组
    //gc方法的执行影响了index值,因此需要重新确定新元素插入的位置
    i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
    }
    //插入新的key和value
    mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
    mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
    mSize++;
    }
    }

可以初始化长度,SparseArray array=new SparseArray<>(5);
SparseIntArray intArray=new SparseIntArray();
SparseBooleanArray booleanArray=new SparseBooleanArray();
SparseLongArray longArray=new SparseLongArray();
来取代相应的HashMap

使用场景:
SparseArray并没有多高深的设计和算法,Android程序中小数据量的场景可以考虑使用。

SparseBooleanArray和SparseIntArray
SparseBooleanArray和SparseIntArray,其实看名字也知道,它们跟SparseArray极其类似,只是存储类型加以限制了。SparseBooleanArray只能存储boolean值,而SparseIntArray只能存储integer类型的值。它们也同样实现了Cloneable接口,可以直接调用clone方法,也同样是以二分法为依据。而其他的主要方法也是一样的。下面以SparseBooleanArray为简单例子写出主要的方法,从方法看出,两者和SparseArray的确是灰常类似的
.

在这里插入图片描述

那么SparseBooleanArray和SparseIntArray也和SparseArray一样,存储不是太多的数据,它们都是作为比HashMap更好的选择。但数据是死的,功能也是死的,实现方式是灵活的。条条大路通罗马。我们不能一概而论说谁好谁差,放在具体的场景,才能选择更高效也更合乎成本的实现方式。

SparseBooleanArray和SparseIntArray
SparseBooleanArray和SparseIntArray,其实看名字也知道,它们跟SparseArray极其类似,只是存储类型加以限制了。SparseBooleanArray只能存储boolean值,而SparseIntArray只能存储integer类型的值。它们也同样实现了Cloneable接口,可以直接调用clone方法,也同样是以二分法为依据。而其他的主要方法也是一样的。下面以SparseBooleanArray为简单例子写出主要的方法,从方法看出,两者和SparseArray的确是灰常类似的
.

在这里插入图片描述

那么SparseBooleanArray和SparseIntArray也和SparseArray一样,存储不是太多的数据,它们都是作为比HashMap更好的选择。但数据是死的,功能也是死的,实现方式是灵活的。条条大路通罗马。我们不能一概而论说谁好谁差,放在具体的场景,才能选择更高效也更合乎成本的实现方式。

猜你喜欢

转载自blog.csdn.net/yaoming168/article/details/88946938
今日推荐