SparseArray和ArrayMap

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

你可能感兴趣的:(SparseArray和ArrayMap)