Java&Android 知识梳理-SparseArray 源码解析

一、基本概念

SparseArray的用法和key为int类型,value为Object类型的HashMap相同,和HashMap相比,先简要介绍一下它的两点优势。

内存占用

在 Java&Android 基础知识梳理(8) - 容器类 我们已经学习过HashMap的内部实现,它内部是采用数组的形式保存每个Entry,并采用链地址法来解决Hash冲突的问题。但是采用数组会遇到扩容的问题,默认情况下当数组内的元素达到loadFactor的时候,会将其扩大为目前大小的两倍,那么就有可能造成空间的浪费。

SparseArray虽然也是采用数组的方式来保存Key/Value

如果有想学习java的程序员,可来我们的java学习扣qun:94311,1692免费送java的视频教程噢!我整理了一份适合18年学习的java干货,送给每一位想学的小伙伴,并且每天晚上8点还会在群内直播讲解Java知识,欢迎大家前来学习哦。

但是与HashMap使用普通数组不同,它对存放Value的mValues数组进行了优化,其创建方式为:

其中ArrayUtils.newUnpaddedObjectArray(initialCapacity)用于创建优化后的数组,该方法实际上是一个Native方法,它解决了当数组中的元素没有填满时造成的空间浪费。

在 SparseArray 浅析 一文中介绍了SparseArray对于数组的优化方式,假设有一个9 x 7的数组,在一般情况下它的存储模型可以表示如下:

可以看到这种模型下的数组当中存在大量无用的0值,内存利用率很低。而优化后的方案用两个部分来表示数组:

第一部分:存放的是数组的行数、列数、当前数组中有效元素的个数

第二部分:存放的是所有有效元素的行、列数、元素的值

mKeys则是用普通数组实现的,通过查找Key值所在的位置,再根据mValues数组的属性找到对应元素的行、列值,从而得到对应的元素值。

避免自动装箱

对于HashMap来说,当我们采用put(1, Object)这样的形式来放入一个元素时,会进行自动装箱,即创建一个Integer对象放入到Entry当中。

SparseArray则不会存在这一问题,因为我们声明的就是int[]类型的mKeys数组。

二、源码解析

2.1 存放过程

2.2 读取过程

2.3 删除过程

可以看到,在删除元素的时候,它是用一个空的Object来标记该位置。在合适的时候(例如上面的put方法),才通过下面的gc()方法对mKeys和mValues数组 重新排列

2.4 二分查找

这里用的是~,由于lo>=0,所以当无法查找到对应元素的时候,返回值~lo一定<0。(~lo=-(lo+1))

这也是我们在2.1中看到,为什么在i>=0时就可以直接替换的原因,因为只要i>=0,就说明之前已经存在一个Key相同的元素了。

而在返回值小于0时,对它再一次取~,就刚好可以得到 要插入的位置

三、SparseArray 的效率问题

了解了SparseArray的原理之后,我们可以分析出有以下几方面有可能会影响SparseArray插入的效率:

插入的效率。插入的效率其实主要跟Key值插入的先后顺序有关,假如Key值是按 递减顺序 插入的,那么每次我们都是在mValues的[0]位置插入元素,这就要求把原来Values和mKeys数组中[0, xxx]位置元素复制到[1, xxx+1]的位置,而如果是 递增插入 的则不会存在该问题,直接扩大数组数组的范围之后再插入即可。

查找的效率。这点很明显,因为采用了二分查找,如果查找的Key值位于折半处,那么将会更快地找到对应的元素。

也就是说SparseArray在插入和查找上,相对于HashMap并不存在明显的优势,甚至在某些情况下,效率还要更差一些。

Google之所以推荐我们使用SparseArray来替换HashMap,是因为在移动端我们的数据集往往都是比较小的,而在这种情况下,这两者效率的差别几乎可以忽略。但是在内存利用率上,由于采用了优化的数组结构,并且避免了自动装箱,SparseArray明显更高,因此更推荐我们使用SparseArray。

四、SparseArray 的衍生

SparseArray还有几个衍生的类,它们的基本思想都是一样的,即:

用两个数组分别存储key和value,通过下标管理映射关系。

采用二分查找法查找现在mKeys数组中对应找到所在元素的下标,再去mValues数组中取出元素。

我们在平时使用的时候,可以根据实际的应用场景选取相应的集合类型。

Key 类型不同

假如key为long型:

LongSparseArray:key为long,value为Object

Value 类型不同

假如key为int,而value为下面三种基本数据类型之一,那么可以采用以下三种集合来避免value的自动装箱来进一步优化。

SparseLongArray:key为int,value为long

SparseBooleanArray:key为int,value为boolean

SparseIntArray:key为int,value为int

Key 和 Value 类型都不同

假如key和value都不为基本数据类型,那么可以采用:

ArrayMap:key为Object,value为Object

架构师视频资料分享链接:

data:text/html;charset=UTF-8;base64,

5p625p6E5biI5a2m5Lmg5Lqk5rWB576k5Y+35pivNTc1NzUxODU0Cg==

复制粘贴在网站即可!

推荐阅读更多精彩内容