【实习周记】ArrayMap源码分析

【实习周记】ArrayMap源码分析

一.概述

ArrayMap是Android专门针对内存优化而设计的,用于取代Java API中的HashMap数据结构。
内部通过两个数组实现,存储结构如下

二.主要方法的源码分析

1.重要字段

(1).private static final int BASE_SIZE = 4; // 容量增量的最小值
(2).private static final int CACHE_SIZE = 10; // 缓存数组的上限

(2).static Object[] mBaseCache; //用于缓存大小为4的ArrayMap
(3).static int mBaseCacheSize; // 记录大小为4的ArrayMap的缓存数量
(4).static Object[] mTwiceBaseCache; //用于缓存大小为8的ArrayMap
(5).static int mTwiceBaseCacheSize; // 记录大小为8的ArrayMap的缓存数量

(6).final boolean mIdentityHashCode; //默认false
(7).int[] mHashes; //由key的hashcode所组成的数组
(8).Object[] mArray; //由key-value对所组成的数组,是mHashes大小的2倍
(9).int mSize; //成员变量的个数

2.构造方法

创建ArrayMap对象时可以指定ArrayMap的长度,默认长度为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).内存分配

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];
}

在这里插入图片描述

3.put方法

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).二分查找

indexOfNull和indexOf方法内部主要通过binarySearch实现,并对返回值进行了一些处理。

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).内存释放

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++;
            }
        }
    }
}

在这里插入图片描述

4.get方法

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());
}

可以看出indexOfKey内部通过调用indexOfNull和indexOf实现,核心也是通过二分查找实现。

5.remove方法

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

remove内部调用indexOfKey,把key转换为index,最后委派给removeAt处理。

(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;
}

三.总结

1.缓存机制——内存分配(allocArrays)和内存释放(freeArrays)

(1).allocArrays触发时机:

当执行ArrayMap的构造函数的情况
当执行removeAt()在满足容量收紧机制的情况
当执行ensureCapacity()在当前容量小于预期容量的情况下, 先执行allocArrays,再freeArrays
当执行put()在容量满的情况下, 先执行allocArrays, 再执行freeArrays

(2).freeArrays()触发时机:

当执行removeAt()移除最后一个元素的情况
当执行clear()清理的情况
当执行ensureCapacity()在当前容量小于预期容量的情况下, 先执行allocArrays,再freeArrays
当执行put()在容量满的情况下, 先执行allocArrays, 再执行freeArrays

2.扩容机制——容量扩张(put)和容量收缩(removeAt)

(1).容量扩张:put

触发:当mSize大于或等于mHashes数组长度时扩容
当map个数满足条件 osize<4时,则扩容后的大小为4;
当map个数满足条件 4<= osize < 8时,则扩容后的大小为8;
当map个数满足条件 osize>=8时,则扩容后的大小为原来的1.5倍;
可见ArrayMap大小在不断增加的过程,size的取值为4,8,12,18,27,40,60,……

(2).容量收缩:removeAt

触发:当数组内存的大小大于8,且已存储数据的个数mSize小于数组空间大小的1/3时,收缩
当mSize<=8,则设置新大小为8;
当mSize> 8,则设置新大小为mSize的1.5倍。
在数据较大的情况下,当内存使用量不足1/3的情况下,内存数组会收紧50%。

发布了10 篇原创文章 · 获赞 5 · 访问量 585

猜你喜欢

转载自blog.csdn.net/LeeDuoZuiShuai/article/details/101287940