ArrayMap源码分析

唉,看源码看源码,泥煤,看的头疼。可是你要是不看,如何出去装逼,如何写出性能比较高的代码,被人一问三不知,那叫一个大写的尴尬,好了废话不多说,慢慢看吧。

源码大致看了一下,还是比较多的,分析之前,还是先看一下他的设计思路,这样下面理解起来也容易。

ArrayMap设计思想:SimpleArrayMap采用了两个数组来进行hash值与key、value值得保存,另外,数组大小超过8时,并需要进行重新分配空间时,只增大当前数组大小的一半,并对大小为4和8的数组进行缓存。这样最后带来的好处就是最大程度保证了数组空间都能够被使用,一定程度上避免了内存空间的浪费。
ArrayMap数据结构:使用了两个数组,一个是Hash数组,另一个是大小*2的Array数组,为了保证通用性,这里所使用的是Object数组。Array数组中使用key+value间隔存取的方式,偶数为即 0 -> key1 1 -> value1 2 -> key2 3 -> value2 。另外Hash数组,则是对应的Key的Hash值数组,并且这是一个有序的int数组,这样在进行Key的查找时,使用二分查找则是最有效率的方式了。

此处输入图片的描述

  • 一如既往从最简单的入手,先看构造方法和全局变量:
    //存储key的hash值
    int[] mHashes;
    //偶数位存key,奇数位存前面的key对应的value,即:mArray[0]=key1,mArray[1] = value1...
    Object[] mArray;
    //mHashes存储的数据量
    int mSize;


  public ArrayMap() {
        super();
    }

    /**
     * Create a new ArrayMap with a given initial capacity.
     */
    public ArrayMap(int capacity) {
        super(capacity);
    }

    /**
     * Create a new ArrayMap with the mappings from the given ArrayMap.
     */
    public ArrayMap(SimpleArrayMap map) {
        super(map);
    }

很简单啥都没有,全部都是继承自父类的,那就追踪到父类看看,

  • 构造方法都干了些嘛:

 public SimpleArrayMap() {
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
        mSize = 0;
    }

    /**
     * Create a new ArrayMap with a given initial capacity.
     */
    public SimpleArrayMap(int capacity) {
        if (capacity == 0) {
            mHashes = ContainerHelpers.EMPTY_INTS;
            mArray = ContainerHelpers.EMPTY_OBJECTS;
        } else {
            allocArrays(capacity);
        }
        mSize = 0;
    }

    /**
     * Create a new ArrayMap with the mappings from the given ArrayMap.
     */
    public SimpleArrayMap(SimpleArrayMap map) {
        this();
        if (map != null) {
            putAll(map);
        }
    }


    static final int[] EMPTY_INTS = new int[0];
    static final Object[] EMPTY_OBJECTS = new Object[0];

也不难理解吧,
1,无参构造方法就是把数组初始化
2,第二个容量构造方法,capacity=0的时候同无参,capacity !=0的时候有一个allocArray()处理,这个等下再说,先把逻辑走通
3,第三个构造除了初始化,还有map的处理,putAll(map),等下再说

  • 下面再看一下数组的释放

    public void clear() {
        if (mSize != 0) {
            freeArrays(mHashes, mArray, mSize);
            mHashes = ContainerHelpers.EMPTY_INTS;
            mArray = ContainerHelpers.EMPTY_OBJECTS;
            mSize = 0;
        }
    }

逻辑很简单吧,跟初始化一样,置空,freeArrays()等下再说

  • 下面来说几个重点方法的实现:

 /**
     * Add a new value to the array map.
     * @param key The key under which to store the value.  <b>Must not be null.</b>  If
     * this key already exists in the array, its value will be replaced.
     * @param value The value to store for the given key.
     * @return Returns the old value that was stored for the given key, or null if there
     * was no such key.
     */
    public V put(K key, V value) {
        final int hash;
        int index;
        //获取key在mHashes数组的下标
        if (key == null) {//查找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;
    }

13-19行:就是获取key对应的hash值,和在mHashes数组中的下标;
21-26行:key原来存在,key对应的value在mArray中的下标为:index<<2+1,oldValue = mArray[index<<2+1],然后替换成新的vaLue即可:mArray[index<<2+1] = value,然后返回oldValue;
28行:就是没找到,index = ~index,这个就是要插入的位置
29-46行:mHashes数组进行重新分配空间,规则:新数组的大小n,当mSize>=8的时候,n = mSize+mSize/2;当 8>mSize>=4,n = 8;mSize<4,n=4;将原数组赋值给两个临时变量存储,重新初始化mHashes,mArray,然后把原数组的值拷贝到新数组中,释放缓存。
48-53行:index在数组之间,也就是这次put的key对应的hashcode排序没有排在最后(index没有指示到数组结尾),因此需要移动index后面的数据
55-58行:存储数据,hash存到mHashes数组中,key,value存到mArray中,这下就跟前面的设计思路对上了吧。

  • put完,是不是就该get了:

 /**
     * Retrieve a value from the array.
     * @param key The key of the value to retrieve.
     * @return Returns the value associated with the given key,
     * or null if there is no such key.
     */
    public V get(Object key) {
        final int index = indexOfKey(key);
        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
    }

这应该不难理解,就是查找key对应的下标,然后返回响应的value,至于里面的算法,放在下面说。

  • 常用的还有个remove吧

 /**
     * Remove an existing key from the array map.
     * @param key The key of the mapping to remove.
     * @return Returns the value that was stored under the key, or null if there
     * was no such key.
     */
    public V remove(Object key) {
        final int index = indexOfKey(key);
        if (index >= 0) {
            return removeAt(index);
        }

        return null;
    }


    public V removeAt(int index) {
        final Object old = mArray[(index << 1) + 1];
        if (mSize <= 1) {
            // Now empty.
            if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
            freeArrays(mHashes, mArray, mSize);
            mHashes = ContainerHelpers.EMPTY_INTS;
            mArray = ContainerHelpers.EMPTY_OBJECTS;
            mSize = 0;
        } else {
        //如果当初申请的数组最大容纳数据个数大于BASE_SIZE的2倍(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.
                final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>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);

                mSize--;
                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 < mSize) {
                    if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
                            + " to " + index);
                    System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
                    System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
                            (mSize - index) << 1);
                }
            } else {
                mSize--;
                if (index < mSize) {
                    if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
                            + " to " + index);
                    System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
                    System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
                            (mSize - index) << 1);
                }
                mArray[mSize << 1] = null;
                mArray[(mSize << 1) + 1] = null;
            }
        }
        return (V)old;
    }

23-29行:如果当前mHashes大小<=1,说明移除key之后,变为空数组,mHashes,mArray都置为空数组,mSize=0;
31-55行:当mHashes初始申请的容量大于8并且存储量小于1/3容量,此时要重新分配空间,减少内存的使用;
35行,重新分配后的数组大小n,如果mSize>8,n = mSize+mSize/2,否则n =8;
41行:这个肯定是重新分配空间后的数组处理
44-48行:把之前的数组ohashes[0,index-1],oarray[0,index*2-1]内的元素重新复制到新的数组中也就是把原数组中index之前的元素复制到新数组中;
49-54行:如果 Index < mSize;再把ohashes[index+1,mSize-index-1],oarray [(index+1)*2,(mSize-index)*2-1]内的元素复制到新数组中,也就是把原数组中index后面的元素复制到新数组中。
是不是发现除了index元素没有复,其余的都复制到了新数组中,这不就是remove实现吗。
57-66行:移除key,mSize–,如果index在mHashes中间,把index后面的数据向前移动,index对应的元素就会被重新赋值index+1对应的元素,依次类推,最后把mArray末尾的key,value置为null.也就是把mHashes[index+1,mSize],mArrays[(index+1)2,(mSize)*2]的元素往前移动一位,这里要注意,并没有把mHashes最后一个元素对应的hashcode置为0;

  • 上面的代码中不是有多处涉及到了indexOf吗,接下来,就看一下indexOf处理了什么:

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

逻辑很明显了,先来看key != null的情况:

int indexOf(Object key, int hash) {
        final int N = mSize;

        // Important fast case: if nothing is in here, nothing to look for.
        if (N == 0) {
            return ~0;
        }

        int index = ContainerHelpers.binarySearch(mHashes, N, hash);

        // If the hash code wasn't found, then we have no entry for this key.
        if (index < 0) {
            return index;
        }

        // If the key at the returned index matches, that's what we want.
        if (key.equals(mArray[index<<1])) {
            return index;
        }

        // Search for a matching key after the index.
        int end;
        for (end = index + 1; end < N && mHashes[end] == hash; end++) {
            if (key.equals(mArray[end << 1])) return end;
        }

        // Search for a matching key before the index.
        for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
            if (key.equals(mArray[i << 1])) return i;
        }

        // Key not found -- return negative value indicating where a
        // new entry for this key should go.  We use the end of the
        // hash chain to reduce the number of array entries that will
        // need to be copied when inserting.
        return ~end;
    }

5-7行:如果没有存储数据就不用找了,直接返回~0(-1);
9行:二分查找法,没什么好说的了吧,找到就返回,找不到就返回如果插入,应该在数组中的位置;
12-14行:mHashes数组中没有这个key的hashcode,但是返回了key的位置(~index);
17-19行:key匹配上mArray中的key,那就是有呗,直接返回。
22-25行:如果key不匹配mArray中对应的key,查询index后面的key,如果匹配,返回end下标;
28-30行:index后面没查到,那就往index前面查,查询index前面的key,如果匹配,返回下标,
36行:如果查询都没有查到,那是真没有,返回负值~end(如果这个key插入,那他在的位置就是end),这个也是有指示作用的;

接着再看一下 key==null的情况:

 int indexOfNull() {
        final int N = mSize;

        // Important fast case: if nothing is in here, nothing to look for.
        if (N == 0) {
            return ~0;
        }

        int index = ContainerHelpers.binarySearch(mHashes, N, 0);

        // If the hash code wasn't found, then we have no entry for this key.
        if (index < 0) {
            return index;
        }

        // If the key at the returned index matches, that's what we want.
        if (null == mArray[index<<1]) {
            return index;
        }

        // Search for a matching key after the index.
        int end;
        for (end = index + 1; end < N && mHashes[end] == 0; end++) {
            if (null == mArray[end << 1]) return end;
        }

        // Search for a matching key before the index.
        for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
            if (null == mArray[i << 1]) return i;
        }

        // Key not found -- return negative value indicating where a
        // new entry for this key should go.  We use the end of the
        // hash chain to reduce the number of array entries that will
        // need to be copied when inserting.
        return ~end;
    }

这个跟上面的那个方法很类似,逻辑其实是一样的,
不同点在:第9行,查询的hashcode =0;
第24行null==mArray[end<<1],第29行 null == mArray[i<<1];
但key为null只能在数组中存在一个的,因为在数据的put操作的时候,会对key进行检查,这样保证了key为null只能存在一个。
这也就是说ArrayMap允许key为null插入

分析到这里,ArrayMap中的几大核心方法已经都明了了,也没什么嘛,很简单吧,最后再来说一下一直都能看到的2个方法是什么鬼:allocArray,freeArray;

  • 源码中看到了两个神奇的数组,他俩主要的目的是对固定的数组来进行缓存,官方给的说法是避免内存抖动,毕竟这里是纯数组来实现的,而当数组容量不够的时候,就需要建立一个新的数组,这样旧的数组不就浪费了,所以这里的缓存还是灰常必要的.

先看数据结构吧

  /**
     * The minimum amount by which the capacity of a ArrayMap will increase.
     * This is tuned to be relatively space-efficient.
     */
    private static final int BASE_SIZE = 4;

    /**
     * Maximum number of entries to have in array caches.
     */
    private static final int CACHE_SIZE = 10;

    /**
     * Caches of small array objects to avoid spamming garbage.  The cache
     * Object[] variable is a pointer to a linked list of array objects.
     * The first entry in the array is a pointer to the next array in the
     * list; the second entry is a pointer to the int[] hash code array for it.
     */
    static Object[] mBaseCache;
    static int mBaseCacheSize;
    static Object[] mTwiceBaseCache;
    static int mTwiceBaseCacheSize;

代码中有两个静态的Object数组,这两个静态数组采用链表的方式来缓存所有的数组。即Object数组会用来指向array数组,而这个array的第一个值为指针,指向下一个array,而第二个值是对应的hash数组,其他的值则为空。另外,缓存数组即baseCache和twiceBaseCache,它俩大小容量的限制:最小值为4,最大值为10,而BaseCache数组主要存储的是容量为4的数组,twiceBaseCache主要存储容量为8的数组。如图:
此处输入图片的描述

  • 缓存数据添加(存)

    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
        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");
                }
            }
        } 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");
                }
            }
        }
    }

    这个方法主要调用的地方在于ArrayMap进行容量改变时,代码中,会对当前数组的array进行清空操作,但第一个值指向之前cache数组,第二个值指向hash数组。

缓存数组的使用(取):

  private void allocArrays(final int size) {
        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;
                }
            }
        } 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;
                }
            }
        }

        mHashes = new int[size];
        mArray = new Object[size<<1];
    }

这个时候,当size跟缓存的数组大小相同,即要么等于4,要么等于8,即可从缓存中拿取数组来用。这里主要的操作就是baseCache指针的移动,指向array[0]指向的指针,hash数组即为array[0],而当前的这个array咱们就可以使用了。

总结:

  1. SimpleArrayMap是可以替代ArrayMap来使用的,区别只是其内部采用单纯的数组来实现,而ArrayMap中采用了EntrySet跟KeySet的结构,这样方便使用 Iterator 来数据的遍历获取。

  2. ArrayMap适用于少量的数据,因为存取的复杂度,对数量过大的就不太合适.我们看一下google的介绍(Note that this implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.)所以最好不要过百条数据

  3. ArrayMap支持key为null,但数组只能有一个key为null的存在。另外,允许多个key的hash值相同,不过尽量避免吧,不然二分查找获取不到,又会进行遍历查找;而key都必须是唯一,不能重复的。
  4. 主要目的是避免占用大量的内存切无法得到地充分利用。
    对容量为4和容量为8的数组,进行缓存,来防止内存抖动的发生。

鉴于水平有限,如果有不正确的地方,请各位大大指出来,提醒我,以免误导别人
参考文章:
1, Android ArrayMap源码详解
2,ArrayMap代码分析
3, SimpleArrayMap源码解析
4,google ArrayMap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值