[Internship] ArrayMap weekly source code analysis

[Internship] ArrayMap weekly source code analysis

I. Overview

ArrayMap Android is designed specifically for memory optimization, and data structures used to replace HashMap in the Java API.
Internal implemented by two arrays, the memory structure is as follows

II. The main source of analysis methods

1. Important fields

(1) .private static final int BASE_SIZE = 4; // minimum capacity increments
(2) .private static final int CACHE_SIZE = 10; // buffer array limit

(2) .static Object [] mBaseCache ; // buffer size for ArrayMap 4
(3) .static int mBaseCacheSize; // record the number of cache size ArrayMap 4
(4) .static Object [] mTwiceBaseCache ; / / ArrayMap for cache size is 8
(5) .static int mTwiceBaseCacheSize; // record the number of cache size ArrayMap 8

(6) .final boolean mIdentityHashCode; // defaults to false
(. 7) .int [] mHashes; // array of key hashcode composed of
(8) .Object [] mArray; // key-value pair consisting of the array, is twice the size of the mHashes
(9) .int mSize; // number of member variables

2. Constructor

You can specify the length of the ArrayMap when creating ArrayMap object, the default length of 0.

    public ArrayMap(int capacity, boolean identityHashCode) {
        mIdentityHashCode = identityHashCode;

        if (capacity < 0) {
            mHashes = EMPTY_IMMUTABLE_INTS;
            mArray = EmptyArray.OBJECT;
        } else if (capacity == 0) {
            mHashes = EmptyArray.INT;
            mArray = EmptyArray.OBJECT;
        } else {
            //分配内存
            allocArrays(capacity);
        }
        mSize = 0;
}

(1) Memory allocation

private void allocArrays(final int size) {
if (size == (BASE_SIZE*2)) {  
//当分配大小为8的对象,先查看缓存池
        synchronized (ArrayMap.class) {
// 当缓存池不为空时
            if (mTwiceBaseCache != null) { 
                final Object[] array = mTwiceBaseCache; 
//从缓存池中取出mArray
                mArray = array;            
                //将缓存池指向上一条缓存地址
mTwiceBaseCache = (Object[])array[0]; 
//从缓存中mHashes
                mHashes = (int[])array[1];
                //清空缓存  
                array[0] = array[1] = null;
                //缓存池大小减1
                mTwiceBaseCacheSize--;  
                return;
            }
        }
    //当分配大小为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--;
                return;
            }
        }
    }
    
    // 分配大小除了4和8之外的情况,则直接创建新的数组
    mHashes = new int[size];
    mArray = new Object[size<<1];
}

Here Insert Picture Description

3.put method

public V put(K key, V value) {
//osize记录当前map大小
    final int osize = mSize; 
    final int hash;
    int index;
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        //获取hashCode
        hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
        //采用二分查找法,从mHashes数组中查找值等于hash的key
        index = indexOf(key, hash); 
    }
    //若index大于零,说明在mHashes中key存在,所以用新的value覆盖旧的value
    if (index >= 0) {
        //index的2倍+1所对应的元素存在相应value的位置
        index = (index<<1) + 1;  
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    //当index<0,说明是新的键值对,对index进行加一取相反数作为新的键值对的位置
index = ~index;
//当mSize大于或等于mHashes数组长度时,需要扩容
    if (osize >= mHashes.length) { 
        final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
                : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        //进行内存分配
        allocArrays(n); 

        //由于ArrayMap并非线程安全的类,不允许并行,如果扩容过程其他线程
//mSize则抛出异常
        if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
            throw new ConcurrentModificationException();
        }

        if (mHashes.length > 0) {
            //将原来老的数组拷贝到新分配的数组
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }
        //释放内存
        freeArrays(ohashes, oarray, osize); 
    }

    //当需要插入的位置不在数组末尾时,需要将index位置后的数据通过拷贝往后移动一位
    if (index < osize) {
     System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
     //这里,index+1比index大1,但是<<1操作扩大二倍后,就相差2了
     //所以不是从index<<1到(index+2)<<1
     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();
        }
    }
    //将hash、key、value添加相应数组的位置,数据个数mSize加1
    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++; 
    return null;
}

(1). Binary search

And internal indexOfNull indexOf method mainly achieved by binarySearch, and a return value of some processing.

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; 
        }
    }
    return ~lo; 
}

(2) Memory release

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    //当释放的是大小为8的对象
    if (hashes.length == (BASE_SIZE*2)) {  
        synchronized (ArrayMap.class) {
            // 当大小为8的缓存池的数量小于10个,则将其放入缓存池
            if (mTwiceBaseCacheSize < CACHE_SIZE) { 
                //array[0]指向原来的缓存池
                array[0] = mTwiceBaseCache;  
                //array[1]存储hash数组
                array[1] = hashes;
                //清空其他数据
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;  
                }
                //mTwiceBaseCache指向新加入缓存池的array
                mTwiceBaseCache = array; 
                //缓存池大小加1
                mTwiceBaseCacheSize++; 
            }
        }
    //当释放的是大小为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++;
            }
        }
    }
}

Here Insert Picture Description

4.get method

public V get(Object key) {
    //根据key找到index,并返回相应值
        final int index = indexOfKey(key);
        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

(1) .indexOfKey

public int indexOfKey(Object key) {
        return key == null ? indexOfNull()
                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
}

It can be seen inside indexOfKey by calling indexOfNull and indexOf, the core is achieved by binary search.

5.remove method

public V remove(Object key) {
    final int index = indexOfKey(key);
    if (index >= 0) {
        return removeAt(index); 
    }
    return null;
}

Remove internal calls indexOfKey, the key is converted to index, and finally delegated to removeAt process.

(1).removeAt

public V removeAt(int index) {
    final Object old = mArray[(index << 1) + 1];
    final int osize = mSize;
    final int nsize;
    //当被移除的是ArrayMap的最后一个元素
    if (osize <= 1) {  
        //释放内存
        freeArrays(mHashes, mArray, osize);
        mHashes = EmptyArray.INT;
        mArray = EmptyArray.OBJECT;
        nsize = 0;
    } else {
        nsize = osize – 1;
        if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
            final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);

            final int[] ohashes = mHashes;
            final Object[] oarray = mArray;
            allocArrays(n); //内存收缩

            //禁止并发
            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
                throw new ConcurrentModificationException();
            }

            if (index > 0) {
                System.arraycopy(ohashes, 0, mHashes, 0, index);
                System.arraycopy(oarray, 0, mArray, 0, index << 1);
            }
            if (index < nsize) {
                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) { //当被移除的元素不是数组最末尾的元素时,则需要将后面的数组往前移动
                System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
                System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
                        (nsize - index) << 1);
            }
            //再将最后一个位置设置为null
            mArray[nsize << 1] = null;
            mArray[(nsize << 1) + 1] = null;
        }
    }
    if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
        throw new ConcurrentModificationException();
    }
    mSize = nsize; //大小减1
    return (V)old;
}

III. Summary

1. Cache mechanism - memory allocation (allocArrays) and memory release (freeArrays)

(1) .allocArrays Trigger timing:

When the implementation of the constructor ArrayMap
when performing removeAt () in the case of the capacity to meet the tightening mechanism
when performing the ensureCapacity () in the current capacity is less than expected capacity, perform allocArrays, then freeArrays
when performing put () at full capacity under the circumstances, the first execution allocArrays, then execute freeArrays

(2) .freeArrays () Trigger timing:

When the implementation removeAt () removes the last element of the
case when performing clear () to clean up
when performing ensureCapacity () in the current capacity is less than expected capacity, perform allocArrays, then freeArrays
when execution put () at full capacity case, execute allocArrays, then execute freeArrays

2. Expansion Mechanism - Capacity expansion (put) and volume contraction (removeAt)

(1) Capacity Expansion: put

Trigger : When mSize mHashes array length equal to or greater than the expansion
when the condition number of the map osize <4, after the expansion of the size 4;
when the number of map satisfies the condition <= osize 8, after the expansion of the size of 4 < 8;
if the number satisfies the condition map osize> = 8, after the expansion of the size of the original 1.5 times;
visible ArrayMap process of increasing the size, size of the value of 4,8,12,18,27, 40, 60, ......

(2) Capacity shrinkage: removeAt

Trigger : When the 1/3 size is larger than the memory array 8, and the number of stored data is smaller than the array Msize space contraction
when mSize <= 8, the new size is set to 8;
when mSize> 8, then set up a new mSize size of 1.5 times.
In the case of larger data, in case the memory usage is less than 1/3 of the memory array will tighten by 50%.

Published 10 original articles · won praise 5 · Views 585

Guess you like

Origin blog.csdn.net/LeeDuoZuiShuai/article/details/101287940