HashMap与ArrayMap的区别2

Posted 灰灰的Rom笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap与ArrayMap的区别2相关的知识,希望对你有一定的参考价值。

接:HashMap与ArrayMap的区别1



ArrayMap 的构成原理

1、ArrayMap 的存储结构。

ArrayMap 中主要存储的数据的是两个数据,

int[] mHashes;
Object[] mArray;

mHashs 中存储出的是每个 key 的 hash 值,并且在这些 key 的 hash 值在数组当中是从小到大排序的。


mArray 的数组长度是 mHashs 的两倍,每两个元素分别是 key 和 value,这两元素对应mHashs 中的 hash 值。mArray 的结构如下图所示。

For Example
key2 和 value2 分别位于数组的第2位和第3位(从0开始计算),对应的是 hash 值就是 hash 的 2/2=1 位,也就是 mHashes[1];

2、arrayMap的get方法。
@Override
public V get(Object key) {
   final int index = indexOfKey(key);
   return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

get 方法其实就是一个计算 index 的过程,计算出来之后如果 index 大于0就代表存在,直接乘以2就是对应的 key 的值,乘以2加1就是对应的 value 的值。

接下来看一下indexOf(Object key,int hash)方法。

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

这里的int index=ContaninerHelpers.bindarySearch(mHashes,N,hash)就是一个二分法查找。因为 key 的 hash 值是从小到大排列的,所以这里可以根据二分法来找对对应 key 的 hash 的位置 index。


然后判断 index 值是否小于0,小于的时候就是代表不包含这个 key 值。
在判断 key 是否一致。这里我估计是考虑到了某些 key 不同,但是 hashcode 有可能相同的情况了。


再往下走,就是根据二分法找到对应位置的 index 值对应的 key 值和我们要找的 key 值不一致的情况了。


因为 hash 在数组当中是从小到大的顺序排列的,所以即使 hash 出现了相同的情况下,也是相邻的。


由于二分法查找只是找到对应的 hashcode 的值,但是如果存在多个相同的 hashcode ,但是 key 不一样的情况下,二分法只能定位到里面随机的一个,但是不可能知道这一个 hashcode 位于这些相同的 hashcode 值中的第几个。


所以,下面的方法就是以当前 index+1 为头,往后挨个寻找,

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

如果找到最后一个 hashcode 一致但是 key 仍不一样,那就反过来以 index-1 位尾,向前挨个的寻找。


如果出现极端情况下,仍然没有找到对应的 key 的情况,那么是说明这个 key 真的不在 arraymap 当中,但是这个 key 对应的 hashcode 是存在于 mHashes 中的。


所以,这时候就返回 ~end ,其实就是返回着一些 hashcode 的序列的最后一个+1。这样有了 endindex 就可以把这个 key 插入到着一些 hashcodes 中的最后一位了。

下图是帮助理解的:

其实 indexOf 方法是会被 get 和 set 方法都调用到的,所以这里既是不存在也返回 index 值,是为了方便 put() 时的插入操作。

3、ArrayMap 的 set 方法和扩容原理。
3.1、put 方法的前几个步骤和 get 是一样的,这里只描述如果不存在这个 key 的情况。

不存在的这个对应的 key 的话,会返回一个 ~index,然后再取反其实就是我们需要插入这个 key 值的位置。

public V put(K key, V value) {
   final int hash;
   int index;
   if (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;
   }
3.2、下面就是扩容判断了

当 size 大于等于 mHashes.length 的时候,进行一个判断,判断需要增加的数量的值n。

final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

两个三元表达式和在一起。
由于BASE_SIZE = 4,解释一下就是
先判断 mSize 值是否大于等于 8,
如果是则n=mSize*1.5
如果否则判断是否大于等于 4,
是则n=8个,否则n=4个。

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

这里代码的作用就是数组复制,如果需要扩容的话,把老的数组中的数据复制到了新的数组当中。


这里有一个freeArrays(ohashes, oarray, mSize);方法是用于内存回收的,下一小节再讲。

3.3、数组移位,插入数据。

我们上面的到了 index 值,就是需要插入新的数据的位置。自然的就需要把 mHashes 中 index 之后的所有的值都后移 1 位,对应的 mArray 全都是 double 移动。


put 方法的前几个步骤和 get 是一样的,这里只描述如果不存在这个 key 的情况。


不存在的这个对应的 key 的话,会返回一个 ~index,然后再取反其实就是我们需要插入这个 key 值的位置。

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

这样就空出来了 index 位置的对象,进行赋值。

put 方法的前几个步骤和 get 是一样的,这里只描述如果不存在这个 key 的情况。

不存在的这个对应的 key 的话,会返回一个 ~index,然后再取反其实就是我们需要插入这个 key 值的位置。

mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
4、arrayMap的内存优化

这个主要是两个方法来控制的。这两个方法看了好久才看明白。
原来关键点其实就是 freeArrays 方法中,传入的是 oldhashes 和 oldarray。以及新的 mSize。

这两个方法的作用基本上就是当长度不够用,我们需要废弃掉老的数组,使用新的数组的时候,把老的数组(包含 mHashes 和 mArray)的数据添加到 oldArray 当中,然后把 oldArray 赋值给 mBaseCache(4个长度),如果再有新的 ArrayMap 创建数组空间的时候,如果还是申请4个的空间,那么优先使用缓存下来的这个。

同理,mTwiceBaseCache 是缓存8个长度的数组空间的。

也就是说,这些缓存空间是留给其它的 ArrayMap 的,而不是当前的 ArrayMap 的。现在网上好多说法什么在数组扩容之后释放本身无用内存空间什么的其实是不准确。

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

······

private void allocArrays(final int size) {
   if (mHashes == EMPTY_IMMUTABLE_INTS) {
       throw new UnsupportedOperationException("ArrayMap is immutable");
   }
   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];
}



未完,请看下一篇:HashMap与ArrayMap的区别3

以上是关于HashMap与ArrayMap的区别2的主要内容,如果未能解决你的问题,请参考以下文章

ArrayMap 和HashMap的区别

ArrayMap和HashMap区别

ArrayMap代码分析

HashMap,ArrayMap,SparseArray源码分析及性能对比

Android内存优化(使用SparseArray和ArrayMap代替HashMap)

数据结构HashMap(Android SparseArray 和ArrayMap)