【Android】SparseIntArray源码解析

学而不思则罔,思而不学则殆


简介

SparseIntArray是Android提供的轻量级的Map<int,int>。SparseIntArray优化了int到int键值对的存储
内部对数据还采取了压缩的方式来表示稀疏数组的数据。
其中key值数组是按照升序排列的数组。

总结

设计目的是优化int到int映射的存储,使用int类型的数组mKeys存储映射的键,使用对应类型的数组mValues存储值
int类型的键在存储上是有顺序的,在查找值时,先使用二分查找,在mKeys中查找值在mValues中的下标,然后返回值
以上三种数据类型和SparseArray最大的区别在于SparseArray在删除元素的时候会将元素设置为DELETED,后续会有gc的过程。

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

优势:

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

缺点:

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

数据结构

public class SparseIntArray implements Cloneable {
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
    private int[] mKeys;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, int)
    private int[] mValues;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
    private int mSize;
    ...
}

在这里插入图片描述
整体采用两个数组,一个是Key值的数组和Values数组

源码分析

构造函数

默认大小是10

    public SparseIntArray() {
        this(10);
    }

也可以自定义大小:

    /**
     * Creates a new SparseIntArray containing no mappings that will not
     * require any additional memory allocation to store the specified
     * number of mappings.  If you supply an initial capacity of 0, the
     * sparse array will be initialized with a light-weight representation
     * not requiring any additional array allocations.
     */
    public SparseIntArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.INT;
        } else {
            mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
            mValues = new int[mKeys.length];
        }
        mSize = 0;
    }

创建一个新的不包含映射的SparseIntArray,它不需要任何额外的内存分配来存储指定数量的映射。如果您提供的初始容量为0,那么稀疏数组将用轻量级表示初始化,而不需要任何额外的数组分配。

测试

通过反射打印数组的状态:

    private void showSparseIntArray() {
        Class<SparseIntArray> sparseIntArrayClass = SparseIntArray.class;
        Log.d("zhangyu", "\n-------------------------------------------------");
        Log.d("zhangyu", "sparseIntArray 1:" + sparseIntArray.size() + " " + sparseIntArray);
        try {
            Field mSize = sparseIntArrayClass.getDeclaredField("mSize");
            Field mKeys = sparseIntArrayClass.getDeclaredField("mKeys");
            Field mValues = sparseIntArrayClass.getDeclaredField("mValues");
            mSize.setAccessible(true);
            Log.d("zhangyu", "Field mSize:" + mSize.get(sparseIntArray));
            mKeys.setAccessible(true);
            int[] keys = (int[]) mKeys.get(sparseIntArray);
            mValues.setAccessible(true);
            int[] values = (int[]) mValues.get(sparseIntArray);
            //打印key值数组情况
            Log.d("zhangyu", "Field mKeys:" + keys.length + " " + Arrays.toString(keys));
            //打印values值数组情况
            Log.d("zhangyu", "Field mValues:" + values.length + " " + Arrays.toString(values));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            Log.d("zhangyu", "e:" + e);
            e.printStackTrace();
        }
        Log.d("zhangyu", "sparseIntArray 2:" + sparseIntArray.size() + " " + sparseIntArray);
        Log.d("zhangyu", "-------------------------------------------------\n");
    }

初始状态

实例化一个SparseIntArray 对象,啥都不干,只是打印初始数组情况:

private SparseIntArray sparseIntArray = new SparseIntArray();

情况如下:

2020-09-27 22:32:41.620 5421-5421/com.example.sparseintarraydemo D/zhangyu: -------------------------------------------------
2020-09-27 22:32:41.620 5421-5421/com.example.sparseintarraydemo D/zhangyu: sparseIntArray 1:0 {}
2020-09-27 22:32:41.622 5421-5421/com.example.sparseintarraydemo D/zhangyu: Field mSize:0
2020-09-27 22:32:41.622 5421-5421/com.example.sparseintarraydemo D/zhangyu: Field mKeys:11 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2020-09-27 22:32:41.622 5421-5421/com.example.sparseintarraydemo D/zhangyu: Field mValues:11 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2020-09-27 22:32:41.622 5421-5421/com.example.sparseintarraydemo D/zhangyu: sparseIntArray 2:0 {}
2020-09-27 22:32:41.622 5421-5421/com.example.sparseintarraydemo D/zhangyu: -------------------------------------------------

size = 0没有问题,每个元素都为0也没有问题,
为啥数组初始长度是11?有点疑惑

查找元素

初始是添加四个元素,如下:

2020-09-28 07:52:06.884 6518-6518/com.example.sparseintarraydemo D/zhangyu: Field mSize:4
2020-09-28 07:52:06.884 6518-6518/com.example.sparseintarraydemo D/zhangyu: Field mKeys:11 [1, 100, 1000, 10000, 0, 0, 0, 0, 0, 0, 0]
2020-09-28 07:52:06.884 6518-6518/com.example.sparseintarraydemo D/zhangyu: Field mValues:11 [1, 100, 1000, 99, 0, 0, 0, 0, 0, 0, 0]
2020-09-28 07:52:06.884 6518-6518/com.example.sparseintarraydemo D/zhangyu: sparseIntArray 2:4 {1=1, 100=100, 1000=1000, 10000=99}

然后通过indexOfKey查找元素的下标:

   /**
     * Returns the index for which {@link #keyAt} would return the
     * specified key, or a negative number if the specified
     * key is not mapped.
     */
    public int indexOfKey(int key) {
        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }

内部实现原理是通过二分查找的原理找到key值下标。时间复杂度 O ( l o g n ) O(logn) O(logn)
如果找到就返回下标,如果没有找到就返回一个负值,但是这个负值不是-1,而是有讲究的,返回值取反就是key真正的应该存在的位置的下标。

查找已经存在的key值(100):

2020-09-28 07:55:39.280 6518-6518/com.example.sparseintarraydemo D/zhangyu: indexOfKey:1

查找不存在的key值下标(50):

2020-09-28 07:58:33.880 6518-6518/com.example.sparseintarraydemo D/zhangyu: sparseIntArray:4 {1=1, 100=100, 1000=1000, 10000=99}
2020-09-28 07:58:33.880 6518-6518/com.example.sparseintarraydemo D/zhangyu: indexOfKey:-2

返回值是-2,-2取反等于多少呢?我们知道在计算机汇总都是补码

-2 原码: 1000 0010
-2 反码: 1111 1101
-2 补码: 1111 1110

-2 取反: 0000 0001  =  1

想了解更多原码,反码,补码请访问:原码,反码,补码概念

所以50的位置应该是1,也就是现在100的位置(如果有的话),这个值很重要,数组移动全都跟这个值相关。

查找不存在的key值下标(5000):

2020-09-28 08:04:04.331 6518-6518/com.example.sparseintarraydemo D/zhangyu: sparseIntArray:4 {1=1, 100=100, 1000=1000, 10000=99}
2020-09-28 08:04:04.331 6518-6518/com.example.sparseintarraydemo D/zhangyu: indexOfKey:-4

换算为:3

查找不存在的key值下标(50000):

2020-09-28 08:04:54.176 6518-6518/com.example.sparseintarraydemo D/zhangyu: sparseIntArray:4 {1=1, 100=100, 1000=1000, 10000=99}
2020-09-28 08:04:54.176 6518-6518/com.example.sparseintarraydemo D/zhangyu: indexOfKey:-5

换算为:4

在测试一个数组长度已满的情况:

D/zhangyu: Field mSize:11
D/zhangyu: Field mKeys:11 [1, 2, 3, 5, 55, 100, 555, 1000, 5555, 10000, 55555]
D/zhangyu: Field mValues:11 [1, 2, 3, 5, 55, 100, 555, 1000, 5555, 99, 55555]
D/zhangyu: sparseIntArray 2:11 {1=1, 2=2, 3=3, 5=5, 55=55, 100=100, 555=555, 1000=1000, 5555=5555, 10000=99, 55555=55555}

此时数组长度已满:
查找55556的位置下标:

2020-09-28 08:08:15.527 6518-6518/com.example.sparseintarraydemo D/zhangyu: sparseIntArray:11 {1=1, 2=2, 3=3, 5=5, 55=55, 100=100, 555=555, 1000=1000, 5555=5555, 10000=99, 55555=55555}
2020-09-28 08:08:15.527 6518-6518/com.example.sparseintarraydemo D/zhangyu: indexOfKey:-12

返回值为-12,说明没有找到,换算后为:11,如果要加入的话,位置下标为11.

添加元素

添加元素1:

2020-09-28 07:45:50.637 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mSize:1
2020-09-28 07:45:50.638 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mKeys:11 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2020-09-28 07:45:50.638 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mValues:11 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

添加2:

2020-09-28 07:47:08.559 6321-6321/com.example.sparseintarraydemo D/zhangyu: sparseIntArray 1:2 {1=1, 2=2}
2020-09-28 07:47:08.559 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mSize:2
2020-09-28 07:47:08.559 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mKeys:11 [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2020-09-28 07:47:08.560 6321-6321/com.example.sparseintarraydemo D/zhangyu: Field mValues:11 [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]

把数组填满:

D/zhangyu: Field mSize:11
D/zhangyu: Field mKeys:11 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
D/zhangyu: Field mValues:11 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
D/zhangyu: sparseIntArray 2:11 {2=2, 4=4, 6=6, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}

测试数组扩容逻辑

再添加一个元素(1):
结果如下:

D/zhangyu: Field mSize:12
D/zhangyu: Field mKeys:23 [2, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
D/zhangyu: Field mValues:23 [2, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
D/zhangyu: sparseIntArray 2:12 {2=2, 4=4, 5=5, 6=6, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}

size = 12,但是数组长度从11变成了23,数组扩容了。

扩容

在这里插入图片描述

元素复制

在这里插入图片描述

添加元素

在这里插入图片描述
插入新增的元素

元素复制

后面的元素后移。
在这里插入图片描述

复制源码逻辑

现在再来追一下源码逻辑:
我用的是add添加元素。

    /**
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * was one.
     */
    public void put(int key, int value) {
        //1.查找新元素位置下标
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 

        if (i >= 0) {
            //2.元素下标为正数,直接修改该位置下标的值
            mValues[i] = value;
        } else {
            //3.取反
            i = ~i;
            //4.添加元素
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }
  1. 通过前面【查找元素】测试,我们知道这里通过二分查找返回的数值i = -3 , 小于 < 0
  2. 取反 i = 2
  3. 插入元素,这才是重点

根据结果我们知道:数组显示经过了扩容,然后元素位置移动,最后在设置新加入的元素。

    /**
     * Primitive int version of {@link #insert(Object[], int, int, Object)}.
     */
    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
        //1.若果新增元素小于当前数组大小,直接元素移动位置
        if (currentSize + 1 <= array.length) {
            //1.1 新增元素后面的同一向后平移一个位置
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            //1.2 插入新的元素
            array[index] = element;
            return array;
        }

        //2. 需要扩容,生成新的数组
        int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        //3. 复制元素
        System.arraycopy(array, 0, newArray, 0, index);
        //4. 插入新的元素
        newArray[index] = element;
        //5. 复制元素
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }
扩容数据长度逻辑
    /**
     * Given the current size of an array, returns an ideal size to which the array should grow.
     * This is typically double the given size, but should not be relied upon to do so in the
     * future.
     */
    public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
    }

小于4的时候,直接为8,其他情况下是现有大小*2.

删除元素

    /**
     * Removes the mapping at the given index.
     */
    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--;
    }
    
    /**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
        //根据二分查找,找出key所对应的数组下标
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        //删除元素,位置移动
        if (i >= 0) {
            removeAt(i);
        }
    }

其中查找元素下标的时间复杂度为 O ( l o g n ) O(logn) O(logn)
删除元素的时间复杂度为 O ( n ) O(n) O(n)

删除结果:

D/zhangyu: -------------------------------------------------
D/zhangyu: add:6
D/zhangyu: indexOfKey:2
D/zhangyu: 删除前:{2=2, 4=4, 6=6, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}
D/zhangyu: 删除后:{2=2, 4=4, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}
D/zhangyu: -------------------------------------------------
D/zhangyu: sparseIntArray 1:10 {2=2, 4=4, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}
D/zhangyu: Field mSize:10
D/zhangyu: Field mKeys:11 [2, 4, 8, 10, 12, 14, 16, 18, 20, 22, 22]
D/zhangyu: Field mValues:11 [2, 4, 8, 10, 12, 14, 16, 18, 20, 22, 22]
D/zhangyu: sparseIntArray 2:10 {2=2, 4=4, 8=8, 10=10, 12=12, 14=14, 16=16, 18=18, 20=20, 22=22}
D/zhangyu: -------------------------------------------------

图形化删除元素逻辑

在这里插入图片描述
删除6,那么就需要把6后面的所有元素都前移一位。
同理对应的values也需要前移一位,才能两个数组意义对应。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值