ArrayMap 解析

1,429 阅读5分钟

引言

ArrayMap是Android专门针对内存优化而设计的,用于取代Java API中的HashMap数据结构。

一、变量

    private static final int BASE_SIZE = 4;  //容量增量的最小值
    private static final int CACHE_SIZE = 10;  //缓存数组的最大值
    
    static Object[] mBaseCache;      //容量为4的缓存集合
    static int mBaseCacheSize;       //容量为4的缓存集合个数
    static Object[] mTwiceBaseCache; //容量为8的缓存集合
    static int mTwiceBaseCacheSize;  //容量为8的缓存集合个数
    int[] mHashes;                   //hash值的集合
    Object[] mArray;                 //key 和value 的集合
    int mSize;                       //成员变量的个数

结构如图所示,1个 hashcode index,其key为 2*index, vaule为2*index+1

二、put & get

public V put(K key, V value) {
    final int osize = mSize;
    final int hash;
    int index;
    
    //通过2分法查找出是否有当前数组中是否有相同的key,其index是什么
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
        index = indexOf(key, hash);
    }
    
    //找到存在的index,其value的位置为mArray数组的 2*index+1,并返回被覆盖的数
    if (index >= 0) {
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }
    
    //当前数组没有储存含有此key的数据
    index = ~index;
    
    //当之前的集合以及达到 mHashes.length 的长度了,便需要进行扩容
    if (osize >= mHashes.length) {
    
        //大于 8 扩容1.5倍,小于8大于4 扩容为8 , 小于4扩容为4
        final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
                : (osize >= 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;
        
        //分配Array 见后面
        allocArrays(n);
    
        if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
            throw new ConcurrentModificationException();
        }
    
        //复制数组到新数组里
        if (mHashes.length > 0) {
            if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }
        
        //释放Arrays
        freeArrays(ohashes, oarray, osize);
    }
    
    if (index < osize) {
        if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
                + " to " + (index+1));
        System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
        System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    }
    
    if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
        if (osize != mSize || index >= mHashes.length) {
            throw new ConcurrentModificationException();
        }
    }
    
    //添加新的index
    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++;
    return null;
 }

put 流程:

  1. 二分法查找是否有相同key的数据,如果有则直接覆盖。

  2. 判断是否需要扩容

  3. 添加新数据

     @Override
     public V get(Object key) {
     
         //二分法查找在 mHashes 中的index值
         final int index = indexOfKey(key);
         
         //返回 mArray 的2n+1值
         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
     }
    

get 流程:

  1. 二分法查找mHashes集合中的index
  2. 对应 mArray 的2n+1位置,即为所需的 value

三、ArrayMap的缓存机制 *

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    
    //当size为8时,并且cache的数量不到10,将 mTwiceBaseCache 赋值给 array[0], 
    //hashes 数组赋值给 array[1], 将其他的参数全部设置为null。
    
    if (hashes.length == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCacheSize < CACHE_SIZE) {
                array[0] = mTwiceBaseCache;
                array[1] = hashes;
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;
                }
                mTwiceBaseCache = array;
                mTwiceBaseCacheSize++;
                if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
                        + " now have " + mTwiceBaseCacheSize + " entries");
            }
        }
    
    //同上,只是此时是size 为4
    } else if (hashes.length == BASE_SIZE) {
        synchronized (ArrayMap.class) {
            if (mBaseCacheSize < CACHE_SIZE) {
                array[0] = mBaseCache;
                array[1] = hashes;
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;
                }
                mBaseCache = array;
                mBaseCacheSize++;
                if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
                        + " now have " + mBaseCacheSize + " entries");
            }
        }
    }
}
 
private void allocArrays(final int size) {
    if (mHashes == EMPTY_IMMUTABLE_INTS) {
        throw new UnsupportedOperationException("ArrayMap is immutable");
    }
    
    //如果size为8,使用 mTwiceBaseCache,将 mTwiceBaseCache 本身给 mArray , 
    //将 mTwiceBaseCache 的第二个参数 给 mHashes 。
    
    if (size == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCache != null) {
                final Object[] array = mTwiceBaseCache;
                mArray = array;
                mTwiceBaseCache = (Object[])array[0];
                mHashes = (int[])array[1];
                array[0] = array[1] = null;
                mTwiceBaseCacheSize--;
                if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
                        + " now have " + mTwiceBaseCacheSize + " entries");
                return;
            }
        }
        
    //同上,只是此时是size 为4
    } else if (size == BASE_SIZE) {
        synchronized (ArrayMap.class) {
            if (mBaseCache != null) {
                final Object[] array = mBaseCache;
                mArray = array;
                mBaseCache = (Object[])array[0];
                mHashes = (int[])array[1];
                array[0] = array[1] = null;
                mBaseCacheSize--;
                if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
                        + " now have " + mBaseCacheSize + " entries");
                return;
            }
        }
    }
    
    //如果没有cache 或者size 不是4、8 会创建新的数组。
    mHashes = new int[size];
    mArray = new Object[size<<1];
}

在put时,先会去进行一次Array的分配(allocArrays),但是在第一次的时候,并没有可用的cache,此时会创建新的数组 hash 与 array。如果有可用的 cache,会将 cache中的 自己(array)和array[1]拿出,分别赋值给 mArraymHashes

然后会进行一次Array的释放(freeArrays,ArrayMap的优点便体现在此处。释放旧的数组时,其实并不是真正的去消除其引用,等待GC回收。而是将旧的数组按照一种特定的方式储存在cache中,将值置为空,如果扩容时需要用到8或者4长度的数组,就不用再去new int[]了,可以直接从cache中取出。

add cache

delete cache

四、removeAt

public V removeAt(int index) {
    final Object old = mArray[(index << 1) + 1];
    final int osize = mSize;
    final int nsize;
    if (osize <= 1) {
        // Now empty.
        if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        mHashes = EmptyArray.INT;
        mArray = EmptyArray.OBJECT;
        freeArrays(ohashes, oarray, osize);
        nsize = 0;
    } else {
        nsize = osize - 1;
        
        //当数组个数大于8,并且当前真正的对象个数小于当前数组最大个数的1/3时,会进行缩容。
        if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
            // Shrunk enough to reduce size of arrays.  We don't allow it to
            // shrink smaller than (BASE_SIZE*2) to avoid flapping between
            // that and BASE_SIZE.
            
            //重新计算size,如果小于8就是8,如果大于8 则是对象个数的1.5倍
            final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);

            if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);

            final int[] ohashes = mHashes;
            final Object[] oarray = mArray;
            allocArrays(n);

            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
                throw new ConcurrentModificationException();
            }

            if (index > 0) {
                if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
                System.arraycopy(ohashes, 0, mHashes, 0, index);
                System.arraycopy(oarray, 0, mArray, 0, index << 1);
            }
            if (index < nsize) {
                if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
                        + " to " + index);
                System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
                System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
                        (nsize - index) << 1);
            }
        } else {
            if (index < nsize) {
                if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
                        + " to " + index);
                System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
                System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
                        (nsize - index) << 1);
            }
            mArray[nsize << 1] = null;
            mArray[(nsize << 1) + 1] = null;
        }
    }
    if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
        throw new ConcurrentModificationException();
    }
    mSize = nsize;
    return (V)old;
}

remove 中最重要的就是,当对象个数小于数组size的1/3时,且数组size大于8,则从新计算容器的大小,并且重新分配Array。

所以在使用ArrayMap时,最好使用new ArrayMap(4) | new ArrayMap(8) 这样可以最大程度的减少对象的创建。

总结

ArrayMap 相比 HashMap 而言

  1. 由于查找是二分法,在数据量大的情况下,查找速率低,千条以下数据可用。
  2. ArrayMap内部有还存在缩容逻辑,更好的去精简内存。