HashMap是比较费内存的,只要一满足扩容条件,HashMap的空间将会以2倍的规律进行增大。假如我们有几十万、几百万条数据,那么HashMap要存储完这些数据将要不断的扩容,而且在此过程中也需要不断的做hash运算,这将对我们的内存空间造成很大消耗和浪费。数据量一大不可避免就产生hash冲突,而HashMap中解决Hash冲突的方法是遍历链表,在数据量很大时候会比较慢。所以在有些情况下我们可以使用SparseArray来代替HashMap来提升性能。
SparseArray
一.先说下SparseArray的特点
1.避免了对key的自动装箱,int转为Integer类型。
2.内部通过两个数组来进行数据存储的,一个存储key,另外一个存储value,这个数组是一个稀疏数组,SparseArray会将数据按照从大到小顺序存储,而且会做不断数组整理操作,所以比较省内存。
//整理开始
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);
}```
3.数据添加和删除的时候都会使用二分查找法确定数据的位置,在获取数据的时候非常快,比HashMap快的多,因为HashMap获取数据是通过遍历Entry链表来得到对应的元素。
// 二分查找
// This is Arrays.binarySearch(), but doesn't do any argument validation.
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
}
//添加数据
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(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++;
}
}
二.SparseArray应用场景
虽说SparseArray性能比较好,但是由于其添加、查找、删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显,将降低至少50%。
所以满足下面两个条件我们可以使用SparseArray代替HashMap
(1)数据量不大,最好在千级以内
(2)key必须为int类型
满足以上两种情况的HashMap可以用SparseArray代替
ArrayMap
ArrayMap也是一个映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Key 和 Value 值,它的原理和SparseArray一样,会对key的Hash使用二分法进行从小到大排序,在添加、删除、查找数据的时候也都是先使用key和key的Hash使用二分法获得相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,在数据量比较大的情况下,它的性能将退化至少50%。
大家注意一下这个put方法存储数据方式非常有意思,以一种特殊的方式来避免hash冲突。
public V put(K key, V value) {
final int hash;
int index;
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = key.hashCode();
index = indexOf(key, hash);
}
if (index >= 0) {
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
index = ~index;
if (mSize >= mHashes.length) {
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
freeArrays(ohashes, oarray, mSize);
}
if (index < mSize) {
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
+ " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
}
将hash值保存在一个数组中,而把key和value保存在另一个数组的不同层次中(偶数层和奇数层对应),即使Hash值有冲突但是key不一样,所以计算出的index位置不同。其实最终的原理还是使用了Hash链来避免冲突,所以这个结构在性能上跟HashMap差不多。
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
获得value时也是用indexOf这个函数进行通过key计算index的值,这样即使产生Hash冲突也不会造成影响。
@Override
public V get(Object key) {
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}
public int indexOfKey(Object key) {
return key == null ? indexOfNull() : indexOf(key,key.hashCode());
}
到这里可能有人会说那Hash数组有什么用呢,它当然有用。不过在这里不做描述,有兴趣的同学可以去看下源码。
总结一下
数据量都在千级以内的情况下
key为int用SparseArray
key为long用LongSparseArray
key为T用ArrayMap