谈谈源码中的SparseArray
在Andorid的源码和第三方库中,偶尔能看到该类,我们先来看一下官方文档的说明如下:
SparseArray map integers to Objects. Unlike a normal array of Objects,there can be gaps in the indices. It is intended to be more memory efficient than using a HashMap to map Integers to Objects, both because it avoids auto-boxing keys and its data structure doesn't rely on an extra entry object for each mapping.
上面的意思是SparseArray 用来替代HashMap Int到Object的这种关系。 它设计的目的是为了比HashMap更加节省内存,这是因为:
- 它避免了键值的自动装箱
- 他的数据结构不需要依赖额外的对象来完成映射。
Note that this container keeps its mappings in an array data structure,using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items,the performance difference is not significant, less than 50%.
上面说了,SparseArray通过二分查找键值,这种实现方式不太适合太多的item。通常情况他是比HashMap慢的,但是如果容器只有数百的item,这个性能损失不太重要,不超过50%。
实际在Android环境中,我们的键值也很少有超过上千的,所以SparseArray我们能在项目中用到的地方还是不少,如果在Android Stuido出现一个黄色警告叫你替换的话,你就可以考虑替换了。因为对于Android 来说,内存往往比较重要一点。
稀疏数组
下面我们分析SparseArray的实现方式,从字面意思上翻译过来就是稀疏数组,首先我们打开源码看一下SparseArray的结构是怎么样的:
public class SparseArray<E> implements Cloneable {
private static final Object DELETED = new Object();
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
private int mSize;
........我是省略的代码哟..........
}
看的出代码中有一个 int[] mKeys 数组, 和一个 Object[] mValues 数组,这两个就是存放键值和对象的地方,mSize是我们存入了多少键值对。下面我们将要用一个非常土的办法来看看这个结构是怎么样的,我们写一段测试代码如下,断点调试。
1 SparseArray<Object> sparseArray = new SparseArray<>();
2 sparseArray.put(1, "11");
3 sparseArray.put(8, "13");
4 sparseArray.put(4, "12");
5 sparseArray.put(0, "30");
我们执行第二行代码以后:
mKey中的结构: {1,0,0,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度
mValues中的结构: {"11"}
mSize: 1
我们执行第三行代码以后:
mKey中的结构: {1,8,0,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度
mValues中的结构: {"11","13"}
mSize: 2
我们执行第三行代码以后:
mKey中的结构: {1,4,8,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度
mValues中的结构: {"11","12","13"}
mSize: 3
我们执行第三行代码以后:
mKey中的结构: {0,1,4,8,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度
mValues中的结构: {"30","11","12","13"}
mSize: 4
从以上的结构中我们可以判断出,如果我们想查找一个键值为4的key,那么首先第一步找到key对应在mKey数组中的index,那么对应的value就在对应mValues中的index位置。再仔细观察上面的mKey中的结构,你会发现mKey中的数字是递增的,那么这保证了我们就可以通过二分查找去找到某一个值在mkey中的位置。ok这个结构分析完毕之后,我们接下来看看,增,删,查,找功能。
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, E value) {
//通过二分查找键值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//如果找到了 就直接替换
if (i >= 0) {
mValues[i] = value;
} else {
//如果没有找到,这个返回的值就是没有找到的mid+1 那么再取反 正好是当前mKey的存有值
//的下一个值,只能说好巧妙~~
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);
}
//GrowingArrayUtils 这个源码看不到,google下源码,就是他会动态增加数组的size,
//通过System.arraycopy 实现的,具体自己去google咯
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
Delete
/**
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
//还是二分查找
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找到了删除
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
Find
/**
* Gets the Object mapped from the specified key, or the specified Object
* if no such mapping has been made.
*/
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
//还是二分查找
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
看了以上的操作,都是通过二分查找来操作的,所以和HashMap相比, 性能还是有所损失的,最后看看GC的操作
GC
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
//循环查找 找到了置空
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
// Log.e("SparseArray", "gc end with " + mSize);
}
总结一下, 就是SpareArray 比HashMap更节约内存,但是性能不如HashMap,在Android系统这内存似金的年代,我们还是应该想尽各种办法去节约内存的。 SpareArray还有一个兄弟 叫LongSparsArray 实现原理也是一样的。
来源:oschina
链接:https://my.oschina.net/u/577876/blog/539213