SparseArray 理解

本文深入解析了Android SparseArray的数据结构,介绍了其如何通过二分查找节省内存并提升性能,与HashMap的对比以及内存对齐原理。重点讨论了其在内存效率和速度上的优势。

一、介绍

SparseArray(稀疏数组) ; Android内部特有的api ; SparseArray更节省内存空间;

android 提供的数据结构, 功能类似HashMap ; 与map不同的是key 只能是int ;

二、使用

SparseArray<Object> sparseArray = new SparseArray<>(0);
        sparseArray.put(0,null);
        sparseArray.put(1,"abc1");
        sparseArray.put(2,new String("abcab2"));
        sparseArray.put(3,3);
        sparseArray.put(4,new Boolean(true));
        sparseArray.put(5,new Object());
        sparseArray.put(8,new String("82abc8"));
        sparseArray.put(20,"abcbc20");
        sparseArray.put(0,"a0");

        int size = sparseArray.size();
        for (int i = 0;i < size;i++) {
            Log.e(TAG, "sparseArraySample: i = " + i + ";value = " + sparseArray.get(sparseArray.keyAt(i)) );
        }

三、代码分析

3.1 成员变量

3.2 构造方法

 

默认初始化容量为 10 ; 可以看到当 initialCapacity 为 0 时,mKeys 和 mValues 为空数组; 否则 mValues 通过 ArrayUtils#newUnpaddedObjectArray 方法获得一个最小容量为 initialCapacity  的数组; 

那为什么不直接new 数组,而是调用newUnpaddedObjectArray 方法呢?这里涉及一个概念下面我们再来看一下;

3.3 get 方法

先通过二分查找找到下标,然后如果如果没有被打上DELETED 标记,能从数据取出,则找到,否则为未找到;有DELETED 标记,代表已被删,回头在gc 的时候会被清理掉;先二分查找,所以查找性能不如HashMap 的直接索引。

3.4 delete 方法

先二分查找,找到就打标记DELETED ,而没有直接删;更改mGarbage ;删除将会涉及到数组元素的移动,所以只是先标记,然后统一删一次; 

3.5 SparseArray 的gc 方法

这个gc 指的是SparseArray 自己的那个方法;gc 意味着整理(数组的移动,有效容量的重新赋值);

将打上DELETED 标记的元素移除,再整理移动元素到前面,再更新mSize 的;

3.6 put 方法

3.7 append 方法

append 是对put 的扩展, 如果场景是在末尾追加的话,推荐用该方法;

在有些场景该方法会省去二分查找,直接在末尾添加。

四、SparseArray 与HashMap 对比

相比HashMap ,SparseArray 会更节省内存,由于使用二分查找,速度上会有劣势,但综合 SparseArray 避免装箱拆箱,相同数据量扩容次数少的优势,又使其在速度上扳回一局,具体情况我们来看数据对比。 

100000 条数据,可见取数据HashMap 要快一点点(从这组数据来看1.8ms);内存占用SparseArray 要节省很多; 

五、内存对齐概念

接下来看一下上面提到的newUnpaddedObjectArray 方法;

5.1 newUnpaddedObjectArray 方法

返回至少为minLength,但可能更大的数组。 增加的大小来自避免在数组之后进行任何填充。 填充量取决于componentType和内存分配器实现而变化,这里就涉及了一个内存对齐的概念。

Java数据类型在内存中使用不同的大小。 如果仅分配所需的容量,则在下一 次内存分配之前,末端的一些空间将不使用,此空间称为填充。 因此,为了 不浪费此空间,将数组做大一点。

5.2 什么是内存对齐

还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。

 

5.3 为什么要进行内存对齐

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。

 

 

 

所以传进去的是11 ,内存图如下(紫色是11 的数组长度所占的空间,蓝色是差一个到80 字节的内存),那么newUnpaddedObjectArray 方法会返回长度为12 的数组。

### SparseArray 判空方法实现 在 Android 中,`SparseArray` 是一种优化的数据结构,用于存储整数键到对象值的映射关系。相比于 `HashMap<Integer, Object>`,它具有更低的内存开销和更高的性能表现,尤其是在频繁插入删除场景下。 对于判断 `SparseArray` 是否为空的操作,可以通过其内部维护的关键字段来完成。以下是具体实现方式: #### 1. 使用官方 API 进行判空 `SparseArray` 提供了一个内置的方法 `size()` 来返回当前容器中有效元素的数量。如果该数量为零,则可以认为此 `SparseArray` 容器为空[^1]。 ```java public boolean isEmpty(SparseArray<?> sparseArray) { return sparseArray.size() == 0; } ``` 这种方法简单高效,推荐优先使用。 --- #### 2. 手动遍历判空 如果不希望通过调用 `size()` 方法间接获取状态,也可以手动遍历整个 `SparseArray` 的键集合并统计非空项数目。这种方式虽然更复杂,但在某些特殊需求下可能有用。 ```java public boolean isEmptyManual(SparseArray<?> sparseArray) { final int size = sparseArray.size(); for (int i = 0; i < size; i++) { if (sparseArray.valueAt(i) != null) { return false; // 存在一个非空值即退出 } } return true; } ``` 需要注意的是,在这种情况下不仅考虑了键的存在性还兼顾到了对应值的状态(是否为 `null`)。因此实际应用时需依据业务逻辑决定如何定义“空”。 --- #### 3. 自定义扩展类增加判空功能 为了增强可读性和复用率,还可以创建一个继承自 `SparseArray<T>` 的子类,并在其基础上添加额外的功能比如直接提供 `isEmpty` 接口。 ```java public class EnhancedSparseArray<T> extends SparseArray<T> { public EnhancedSparseArray() {} /** * Check whether this array contains any mappings. */ public boolean isEmpty() { return super.size() == 0; } /** * More strict check including values being non-null. */ public boolean isStrictlyEmpty() { final int size = super.size(); for (int i = 0; i < size; ++i) { if (super.valueAt(i) != null) { return false; } } return true || super.isEmpty(); // Combine both checks as needed } } ``` 这样做的好处在于封装良好且易于理解,缺点则是稍微增加了代码量以及潜在的学习成本给其他开发者了解新类型的意义所在[^4]。 --- ### 总结 综上所述,最简便的方式还是利用现有的 `size()` 函数来进行基本判定;而对于更加严格的要求则可以选择第二种或者第三种方案视具体情况而定。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值