SparseIntArray原理分析

本文深入解析SparseIntArray的实现原理,探讨其存储结构、查找、添加及删除元素的方法。SparseIntArray优化了int到int键值对的存储,通过二分查找提高查找效率,适用于键值对数量较大的场景。

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

系列文章地址:
Android容器类-ArraySet原理解析(一)
Android容器类-ArrayMap原理解析(二)
Android容器类-SparseArray原理解析(三)
Android容器类-SparseIntArray原理解析(四)

SparseArray优化了intObject键值对的存储,SparseIntArray优化了intint键值对的存储。android中在键值对存储上的优化主要做了一下几种类型的优化:

  • int --> Object(SparseArray)
  • int --> int(SparseIntArray)
  • int --> boolean(SparseBooleanArray)
  • int --> long(SparseLongArray)
  • int --> Set(SparseSetArray)

SparseSetArray目前在sdk中还处于hide状态,故在做总结的时候就不分析它了。

之前已经分析过SparseArray,本文就分析下SparseIntArray的实现,并在最后总结下这几种键值对在实现上的共同点。

继承结构


以上为SparseIntArray的继承体系。SparseIntArray只实现了Cloneable接口,结构比较简单。其实阅读源码可以发现,SparseArraySparseIntArraySparseBooleanArraySparseLongArray都只实现了Cloneable接口。

存储结构


以上为SparseIntArray的存储结构,mKeys存储的是int类型的键,mValues存储的是int类型的value。

查找元素

    // 查找键key在mKeys的下标
    public int indexOfKey(int key) {
        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }
    
    // 查找value在mValues的下标
    public int indexOfValue(int value) {
        for (int i = 0; i < mSize; i++)
            if (mValues[i] == value)
                return i;
        return -1;
    }
复制代码

元素的查找分键查找和值查找,键查找使用二分查找,值查找直接使用循环遍历。

添加元素

    public void put(int key, int value) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;

            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }
复制代码

添加元素首先使用二分查找找到key在mKeys数组的下标,也就是value在mValues数组的下标。如果ContainerHelpers.binarySearch(mKeys,mSize,key)在mKeys数组中没有找到key,则返回key待插入位置的下标的取反,如果找到了key,则直接更新mValues对应位置的值即可。 GrowingArrayUtils.insert函数的实现如下:

    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
    
        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
    
        int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }
复制代码

函数的逻辑很简单,首先断言了currentSize <= array.length;如果array在不需要扩大容量的情况下可以添加一个元素,则先将待插入位置index开始的元素整体后移一位,然后插入元素,否则先扩容,然后将元素拷贝到新的数组中。

删除元素

    public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        if (i >= 0) {
            removeAt(i);
        }
    }
    
    public void removeAt(int index) {
        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
        mSize--;
    }
复制代码

删除元素主要涉及以上两个方法,delete(int key)根据key进行删除,removeAt(int index)删除指定下标的元素。这两个方法都是public,故都可以直接使用。delete(int key),先使用二分查找,找到keymKeys的下标,如果找到即i >= 0,则直接删除mKeysmValues指定位置的元素。

总结

SparseBooleanArray,SparseLongArray还没有分析,他们的实现规则是一样的,只是存储的数据类型的mValues数组是booleanlong,接下来就对SparseIntArray,SparseBooleanArray,SparseLongArray进行下总结。

  • 他们的设计目的是优化intint, boolean ,long映射的存储
  • 使用int类型的数组mKeys存储映射的键,使用对应类型的数组mValues存储值
  • int类型的键在存储上是有顺序的
  • 在查找值时,先使用二分查找,在mKeys中查找值在mValues中的下标,然后返回值

以上三种数据类型和SparseArray最大的区别在于SparseArray在删除元素的时候会将元素设置为DELETED,后续会有gc的过程。

相对于使用HashMap,这样的设计的优势和缺点:

优势:

  • 避免int类型的键自动装箱
  • 相较于HashMap使用Node,这样的设计使用更小的存储单元即可存储keyvalue的映射

缺点:

  • 在进行元素查找时使用二分查找,元素较多(谷歌给出的数字是大于1000)时,查找效率较低
  • 在进行元素的添加和删除时,可能会频繁进行元素的移动,运行效率可能会降低

关注微信公众号,最新技术干货实时推送

### SparseIntArray 的基本概念 `SparseIntArray` 是 Android 提供的一种高效的数据结构,用于存储整数键到整数值的映射关系。相比于传统的 `HashMap<Integer, Integer>`,`SparseIntArray` 更加轻量级,在处理稀疏数组时性能更优[^4]。 以下是其主要特点: - **内存占用低**:由于它是专门为整型设计的,因此相比泛型容器(如 HashMap),它的内存开销更低。 - **查找速度快**:通过二分查找实现快速访问操作。 - **适用场景**:适用于需要频繁查询且数据分布较为稀疏的情况。 --- ### 使用示例 下面是一个简单的代码示例展示如何创建和使用 `SparseIntArray`: ```java import android.util.SparseIntArray; public class Example { public static void main(String[] args) { // 创建一个新的 SparseIntArray 实例 SparseIntArray sparseIntArray = new SparseIntArray(); // 添加键值对 sparseIntArray.put(1, 10); sparseIntArray.put(5, 20); sparseIntArray.put(10, 30); // 获取指定 key 对应的 value int valueAtKey1 = sparseIntArray.get(1); // 返回 10 System.out.println("Value at key=1: " + valueAtKey1); // 如果不存在该 key,则返回默认值 (这里是 0) int valueAtKey3 = sparseIntArray.get(3); // 返回 0 System.out.println("Value at non-existent key=3: " + valueAtKey3); // 遍历所有的键值对 for (int i = 0; i < sparseIntArray.size(); i++) { int key = sparseIntArray.keyAt(i); int value = sparseIntArray.valueAt(i); System.out.println("Key=" + key + ", Value=" + value); } // 删除某个键值对 sparseIntArray.delete(5); System.out.println("After deleting key=5:"); for (int i = 0; i < sparseIntArray.size(); i++) { int key = sparseIntArray.keyAt(i); int value = sparseIntArray.valueAt(i); System.out.println("Key=" + key + ", Value=" + value); } } } ``` 上述代码展示了以下几个功能: - 如何向 `SparseIntArray` 中添加键值对; - 如何获取特定键对应的值; - 当键不存在时的行为; - 如何遍历整个集合中的所有键值对; - 如何删除某一个键及其对应值。 --- ### 常见问题及解决方案 #### 1. 性能对比 如果开发者不确定应该选择 `HashMap<Integer, Integer>` 还是 `SparseIntArray`,可以考虑以下几点: - 数据规模较小或者非常稀疏的情况下推荐使用 `SparseIntArray`。 - 如果涉及复杂的哈希计算逻辑或非整型键值对,则需选用 `HashMap` 或其他通用 Map 结构[^5]。 #### 2. 错误处理 当尝试访问未定义的索引时,默认会返回零而不是抛出异常。这可能引发潜在错误,所以在实际开发过程中需要注意初始化合理的默认值来规避此类风险。 #### 3. 替代方案 对于某些特殊需求来说,还有其他的替代品可供选择,比如 SparseBooleanArray 和 LongSparseArray 等专为不同类型的原始数据类型而定制化的类库[^6]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值