ArrayMap源码解析

ArrayMap 使用2个数组。它的对象实例内部有用来存储对象的 Object[] mArray 和 存储哈希值的 int[] mHashes。当插入一个键值对时:

  • 键/值被自动装箱。
  • 键对象被插入到 mArray[] 数组中的下一个空闲位置。
  • 值对象也会被插入到 mArray[] 数组中与键对象相邻的位置。
  • 键的哈希值会被计算出来并被插入到 mHashes[] 数组中的下一个空闲位置。

对于查找一个 key :

  • 键的哈希值先被计算出来
  • 在 mHashes[] 数组中二分查找此哈希值。这表明查找的时间复杂度增加到了 O(logN)。
  • 一旦得到了哈希值所对应的索引 index,键值对中的键就存储在 mArray[2index] ,值存储在 mArray[2index+1]。
  • 相比HashMap,这里的时间复杂度从 O(1) 上升到 O(logN),但是内存效率提升了。当我们在 100 左右的数据量范围内尝试时,没有耗时的问题,察觉不到时间上的差异,但我们应用的内存效率获得了提高。
@Override
public V put(K key, V value) {
    final int osize = mSize;
    final int hash;
    int index;
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
        // 查找下标
        index = indexOf(key, hash);
    }
    // 大于0说明找到对应的下标,替换value即可,并且返回原来的值
    if (index >= 0) {
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    /**
     * 没有找到或者hashCode相同,但value没有equals的情况
     */
    index = ~index;
    // 如果存放hash值的数组空间快不够(<=osize)了,就进入扩容流程
    if (osize >= mHashes.length) {
        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;
        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);
        }

        freeArrays(ohashes, oarray, osize);
    }

    /**
     * 如果hash碰撞的正好是数组的最后一个,那就不需要调整数组,
     * 如果不是,那就进入流程,把index到最后一位全往后挪一位,给index位置空出来,方便后面赋值
     * 这样就说明了,当发生碰撞的时候,新的一条数据是就近插入在碰撞index+1(见{@link #indexOf}返回值end的值变化)的位置,
     */
    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();
        }
    }
    // 插入数据
    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++;
    return 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;
    }

    // 二分法查找法去匹配hashCode,时间复杂度 O(logN)
    int index = binarySearchHashes(mHashes, N, hash);

    // If the hash code wasn't found, then we have no entry for this key.
    // 如果找不到就返回一个负数 -1
    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.
    // 如果hashCode一样,值不一样,进入hash碰撞的流程
    int end;
    // 往后查找相邻的hashCode值相同的,看有没有插入过该值
    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.
    // 往前查找相邻的hashCode值相同的,看有没有插入过该值
    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.
    // 返回负值,既可以用来表示无法找到匹配的key,也可以用来为后续的插入数据所用。
    // end的值是后面用来插入的下标,这就说明了发生hash碰撞的那几个值总是在数组相邻的位置
    return ~end;
}

// TODO: 2018/3/15 扩容的源码会有空加上

参考

https://zhuanlan.zhihu.com/p/23224444
https://www.youtube.com/watch?v=ORgucLTtTDI
https://droidyue.com/blog/2017/02/12/dive-into-arraymap-in-android/

https://www.jianshu.com/p/7b9a1b386265

你可能感兴趣的:(ArrayMap源码解析)