一、概述
最近在项目中看到了SparseArray,好奇研究了下。 SparseArray是Android框架独有的类,在标准的JDK中不存在这个类。它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray不需要对key和value进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。
二、详解
单纯从字面上来理解,SparseArray指的是稀疏数组(Sparse array),所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。假设有一个9*7的数组,其内容如下:
在此数组中,共有63个空间,但却只使用了5个元素,造成58个元素空间的浪费。以下我们就使用稀疏数组重新来定义这个数组:
三、代码
下面分析下SparseArray到底是什么东西?
SparseArray<String> sa = new SparseArray<>();
sa.put(1,"world");
进到put源码里看下
public void put(int key, E value) {
int i = ContainerHelpers.<span style="color:#ff0000;">binarySearch</span>(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
binarySearch很明显是二分查找,首先put先通过二分查找,看目标在不在我们的sparseArray当中,如果已经存在了的话,那么直接替换掉旧的,如果不存在,就直接保存,我们再看下get方法:
public E get(int key, E <span style="color:#ff0000;">valueIfKeyNotFound</span>) {
int i = ContainerHelpers.<span style="color:#ff0000;">binarySearch</span>(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
</pre>valueIfKeyNotFound是取不到结果时候的默认值,首先get方法也是通过binarySearch进行查找的,那么问题来了,对于大规模数量的put,每一次put都要进行按序排列,如果key值恰恰是按照从小到大的顺序排列的,那么SparseArray无非是最好的存储方式,如果key值按照从大到小的方式排列,那么就会造成每一次浪费大量时间进行数据重排。</div><div><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 10px; padding-bottom: 10px; font-size: 14px; line-height: 26px; color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Verdana, 宋体;">ArrayMap</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 10px; padding-bottom: 10px; font-size: 14px; line-height: 26px; color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Verdana, 宋体;">这个api的资料在网上可以说几乎没有,然并卵,只能看文档了 ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 10px; padding-bottom: 10px; font-size: 14px; line-height: 26px; color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Verdana, 宋体;">添加数据</p><pre style="color: rgb(51, 51, 51); overflow-y: auto; border: 1px solid rgb(204, 204, 204); line-height: 25px; border-radius: 3px; padding: 10px; background-color: rgb(245, 245, 245);"><code>public V put(K key, V value)</code>
获取数据
public V get(Objectkey)
删除数据
public V remove(Objectkey)
特有方法
它和SparseArray一样同样也有两个更方便的获取数据方法:
public K keyAt(int index)
public V valueAt(int index)
ArrayMap应用场景
- 数据量不大,最好在千级以内
- 数据结构类型为Map类型
ArrayMap<Key,Value> arrayMap = new ArrayMap<>();
总结
SparseArray和ArrayMap都差不多,使用哪个呢?
假设数据量都在千级以内的情况下:
1、如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用
2、如果key类型为其它的类型,则使用ArrayMap
四、总结
虽然从时间上考虑,SparseArray不一定是最好的选择,但是从内存开销上来看,SparseArray采用稀疏数组的方式还是节约了很大的内存开销,所以我们还是采取Android官方的建议。今后多使用SparseArray进行编程。