SparseArray
的用法和key
为int
类型,value
为Object
类型的HashMap
相同,和HashMap
相比,先简要介绍一下它的两点优点。java
在 Java&Android 基础知识梳理(8) - 容器类 咱们已经学习过HashMap
的内部实现,它内部是采用数组的形式保存每一个Entry
,并采用链地址法来解决Hash
冲突的问题。可是采用数组会遇到扩容的问题,默认状况下当数组内的元素达到loadFactor
的时候,会将其扩大为目前大小的两倍,那么就有可能形成空间的浪费。数组
SparseArray
虽然也是采用数组的方式来保存Key/Value
学习
private int[] mKeys;
private Object[] mValues;
复制代码
可是与HashMap
使用普通数组不一样,它对存放Value
的mValues
数组进行了优化,其建立方式为:优化
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
//默认状况下,建立的 initialCapacity 大小为 10。
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
复制代码
其中ArrayUtils.newUnpaddedObjectArray(initialCapacity)
用于建立优化后的数组,该方法其实是一个Native
方法,它解决了当数组中的元素没有填满时形成的空间浪费。spa
在 SparseArray 浅析 一文中介绍了SparseArray
对于数组的优化方式,假设有一个9 x 7
的数组,在通常状况下它的存储模型能够表示以下:.net
能够看到这种模型下的数组当中存在大量无用的0
值,内存利用率很低。而优化后的方案用两个部分来表示数组:指针
mKeys
则是用普通数组实现的,经过查找
Key
值所在的位置,再根据
mValues
数组的属性找到对应元素的行、列值,从而获得对应的元素值。
对于HashMap
来讲,当咱们采用put(1, Object)
这样的形式来放入一个元素时,会进行自动装箱,即建立一个Integer
对象放入到Entry
当中。code
SparseArray
则不会存在这一问题,由于咱们声明的就是int[]
类型的mKeys
数组。cdn
public void put(int key, E value) {
//经过二分查找法进行查找插入元素所在位置。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//若是大于0,那么直接插入。
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);
}
//从新分配数组,并插入新的 Key,Value。
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
复制代码
public E get(int key, E valueIfKeyNotFound) {
//经过二分查找,在 Key 数组中获得对应 Value 的下标。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//取出下标对应的元素。
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
复制代码
public void delete(int key) {
//二分查找所在位置。
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//将该位置的元素置为 DELETED,它是内部预先定义好的一个对象。
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
复制代码
能够看到,在删除元素的时候,它是用一个空的Object
来标记该位置。在合适的时候(例如上面的put
方法),才经过下面的gc()
方法对mKeys
和mValues
数组 从新排列。对象
private void gc() {
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;
}
复制代码
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
final int midVal = array[mid];
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
}
//若是没有找到,那么返回的是低位指针的取反坐标。
return ~lo; // value not present
}
复制代码
这里用的是~
,因为lo>=0
,因此当没法查找到对应元素的时候,返回值~lo
必定<0
。(~lo=-(lo+1)
)
这也是咱们在2.1
中看到,为何在i>=0
时就能够直接替换的缘由,由于只要i>=0
,就说明以前已经存在一个Key
相同的元素了。
而在返回值小于0
时,对它再一次取~
,就恰好能够获得 要插入的位置。
了解了SparseArray
的原理以后,咱们能够分析出有如下几方面有可能会影响SparseArray
插入的效率:
Key
值插入的前后顺序有关,假如Key
值是按 递减顺序 插入的,那么每次咱们都是在mValues
的[0]
位置插入元素,这就要求把原来Values
和mKeys
数组中[0, xxx]
位置元素复制到[1, xxx+1]
的位置,而若是是 递增插入 的则不会存在该问题,直接扩大数组数组的范围以后再插入便可。Key
值位于折半处,那么将会更快地找到对应的元素。也就是说SparseArray
在插入和查找上,相对于HashMap
并不存在明显的优点,甚至在某些状况下,效率还要更差一些。
Google
之因此推荐咱们使用SparseArray
来替换HashMap
,是由于在移动端咱们的数据集每每都是比较小的,而在这种状况下,这二者效率的差异几乎能够忽略。可是在内存利用率上,因为采用了优化的数组结构,而且避免了自动装箱,SparseArray
明显更高,所以更推荐咱们使用SparseArray
。
SparseArray
还有几个衍生的类,它们的基本思想都是同样的,即:
key
和value
,经过下标管理映射关系。mKeys
数组中对应找到所在元素的下标,再去mValues
数组中取出元素。咱们在平时使用的时候,能够根据实际的应用场景选取相应的集合类型。
假如key
为long
型:
LongSparseArray
:key
为long
,value
为Object
假如key
为int
,而value
为下面三种基本数据类型之一,那么能够采用如下三种集合来避免value
的自动装箱来进一步优化。
SparseLongArray
:key
为int
,value
为long
SparseBooleanArray
:key
为int
,value
为boolean
SparseIntArray
:key
为int
,value
为int
假如key
和value
都不为基本数据类型,那么能够采用:
ArrayMap
:key
为Object
,value
为Object