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

SparseArray是Android轻量级键值对数据结构,节省内存,适合小规模数据。有序key数组,插入查找高效,但插入删除较耗时。本文介绍其特点、使用方法、源码分析及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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<String> 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:AAAA
value at 1:BBBB
value at 2:DDDD
value 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:BBBB
value 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,已经被删除,因此返回null
get 4:CCCC

查找

指定key或者value来查找其下标值:
1. indexOfKey二分法从mKeys数组中查找key的下标
2. 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:1
index of value BBBB:0

源码分析

SparseArray所实现的Cloneable接口,其实是一个空接口,并没有实现什么特性。

public class SparseArray<E> 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并没有多高深的设计和算法,Android程序中小数据量的场景可以考虑使用。
暂时分析到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值