关于Android中ArrayMap/SparseArray比HashMap性能好的深入研究

由于网上有朋友对于这个问题已经有了很详细的研究,所以我就不班门弄斧了:

转载于:http://android-performance.com/android/2014/02/10/android-sparsearray-vs-hashmap.html

    http://liuzhichao.com/p/832.html

http://www.codes51.com/article/detail_163576.html

源码:

  1 /*
  2  * Copyright (C) 2006 The Android Open Source Project
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 package android.util;
 17 import com.android.internal.util.ArrayUtils;
 18 import com.android.internal.util.GrowingArrayUtils;
 19 import libcore.util.EmptyArray;
 20 /**
 21  * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
 22  * there can be gaps in the indices.  It is intended to be more memory efficient
 23  * than using a HashMap to map Integers to Objects, both because it avoids
 24  * auto-boxing keys and its data structure doesn't rely on an extra entry object
 25  * for each mapping.
 26  *
 27  * <p>Note that this container keeps its mappings in an array data structure,
 28  * using a binary search to find keys.  The implementation is not intended to be appropriate for
 29  * data structures
 30  * that may contain large numbers of items.  It is generally slower than a traditional
 31  * HashMap, since lookups require a binary search and adds and removes require inserting
 32  * and deleting entries in the array.  For containers holding up to hundreds of items,
 33  * the performance difference is not significant, less than 50%.</p>
 34  *
 35  * <p>To help with performance, the container includes an optimization when removing
 36  * keys: instead of compacting its array immediately, it leaves the removed entry marked
 37  * as deleted.  The entry can then be re-used for the same key, or compacted later in
 38  * a single garbage collection step of all removed entries.  This garbage collection will
 39  * need to be performed at any time the array needs to be grown or the the map size or
 40  * entry values are retrieved.</p>
 41  *
 42  * <p>It is possible to iterate over the items in this container using
 43  * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
 44  * <code>keyAt(int)</code> with ascending values of the index will return the
 45  * keys in ascending order, or the values corresponding to the keys in ascending
 46  * order in the case of <code>valueAt(int)</code>.</p>
 47  */
 48 public class SparseArray<E> implements Cloneable {
 49     private static final Object DELETED = new Object();
 50     private boolean mGarbage = false;
 51     private int[] mKeys;
 52     private Object[] mValues;
 53     private int mSize;
 54     /**
 55      * Creates a new SparseArray containing no mappings.
 56      */
 57     public SparseArray() {
 58         this(10);
 59     }
 60     /**
 61      * Creates a new SparseArray containing no mappings that will not
 62      * require any additional memory allocation to store the specified
 63      * number of mappings.  If you supply an initial capacity of 0, the
 64      * sparse array will be initialized with a light-weight representation
 65      * not requiring any additional array allocations.
 66      */
 67     public SparseArray(int initialCapacity) {
 68         if (initialCapacity == 0) {
 69             mKeys = EmptyArray.INT;
 70             mValues = EmptyArray.OBJECT;
 71         } else {
 72             mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
 73             mKeys = new int[mValues.length];
 74         }
 75         mSize = 0;
 76     }
 77     @Override
 78     @SuppressWarnings("unchecked")
 79     public SparseArray<E> clone() {
 80         SparseArray<E> clone = null;
 81         try {
 82             clone = (SparseArray<E>) super.clone();
 83             clone.mKeys = mKeys.clone();
 84             clone.mValues = mValues.clone();
 85         } catch (CloneNotSupportedException cnse) {
 86             /* ignore */
 87         }
 88         return clone;
 89     }
 90     /**
 91      * Gets the Object mapped from the specified key, or <code>null</code>
 92      * if no such mapping has been made.
 93      */
 94     public E get(int key) {
 95         return get(key, null);
 96     }
 97     /**
 98      * Gets the Object mapped from the specified key, or the specified Object
 99      * if no such mapping has been made.
100      */
101     @SuppressWarnings("unchecked")
102     public E get(int key, E valueIfKeyNotFound) {
103         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
104         if (i < 0 || mValues[i] == DELETED) {
105             return valueIfKeyNotFound;
106         } else {
107             return (E) mValues[i];
108         }
109     }
110     /**
111      * Removes the mapping from the specified key, if there was any.
112      */
113     public void delete(int key) {
114         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
115         if (i >= 0) {
116             if (mValues[i] != DELETED) {
117                 mValues[i] = DELETED;
118                 mGarbage = true;
119             }
120         }
121     }
122     /**
123      * @hide
124      * Removes the mapping from the specified key, if there was any, returning the old value.
125      */
126     public E removeReturnOld(int key) {
127         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
128         if (i >= 0) {
129             if (mValues[i] != DELETED) {
130                 final E old = (E) mValues[i];
131                 mValues[i] = DELETED;
132                 mGarbage = true;
133                 return old;
134             }
135         }
136         return null;
137     }
138     /**
139      * Alias for {@link #delete(int)}.
140      */
141     public void remove(int key) {
142         delete(key);
143     }
144     /**
145      * Removes the mapping at the specified index.
146      */
147     public void removeAt(int index) {
148         if (mValues[index] != DELETED) {
149             mValues[index] = DELETED;
150             mGarbage = true;
151         }
152     }
153     /**
154      * Remove a range of mappings as a batch.
155      *
156      * @param index Index to begin at
157      * @param size Number of mappings to remove
158      */
159     public void removeAtRange(int index, int size) {
160         final int end = Math.min(mSize, index + size);
161         for (int i = index; i < end; i++) {
162             removeAt(i);
163         }
164     }
165     private void gc() {
166         // Log.e("SparseArray", "gc start with " + mSize);
167         int n = mSize;
168         int o = 0;
169         int[] keys = mKeys;
170         Object[] values = mValues;
171         for (int i = 0; i < n; i++) {
172             Object val = values[i];
173             if (val != DELETED) {
174                 if (i != o) {
175                     keys[o] = keys[i];
176                     values[o] = val;
177                     values[i] = null;
178                 }
179                 o++;
180             }
181         }
182         mGarbage = false;
183         mSize = o;
184         // Log.e("SparseArray", "gc end with " + mSize);
185     }
186     /**
187      * Adds a mapping from the specified key to the specified value,
188      * replacing the previous mapping from the specified key if there
189      * was one.
190      */
191     public void put(int key, E value) {
192         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
193         if (i >= 0) {
194             mValues[i] = value;
195         } else {
196             i = ~i;
197             if (i < mSize && mValues[i] == DELETED) {
198                 mKeys[i] = key;
199                 mValues[i] = value;
200                 return;
201             }
202             if (mGarbage && mSize >= mKeys.length) {
203                 gc();
204                 // Search again because indices may have changed.
205                 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
206             }
207             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
208             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
209             mSize++;
210         }
211     }
212     /**
213      * Returns the number of key-value mappings that this SparseArray
214      * currently stores.
215      */
216     public int size() {
217         if (mGarbage) {
218             gc();
219         }
220         return mSize;
221     }
222     /**
223      * Given an index in the range <code>0...size()-1</code>, returns
224      * the key from the <code>index</code>th key-value mapping that this
225      * SparseArray stores.
226      *
227      * <p>The keys corresponding to indices in ascending order are guaranteed to
228      * be in ascending order, e.g., <code>keyAt(0)</code> will return the
229      * smallest key and <code>keyAt(size()-1)</code> will return the largest
230      * key.</p>
231      */
232     public int keyAt(int index) {
233         if (mGarbage) {
234             gc();
235         }
236         return mKeys[index];
237     }
238     /**
239      * Given an index in the range <code>0...size()-1</code>, returns
240      * the value from the <code>index</code>th key-value mapping that this
241      * SparseArray stores.
242      *
243      * <p>The values corresponding to indices in ascending order are guaranteed
244      * to be associated with keys in ascending order, e.g.,
245      * <code>valueAt(0)</code> will return the value associated with the
246      * smallest key and <code>valueAt(size()-1)</code> will return the value
247      * associated with the largest key.</p>
248      */
249     @SuppressWarnings("unchecked")
250     public E valueAt(int index) {
251         if (mGarbage) {
252             gc();
253         }
254         return (E) mValues[index];
255     }
256     /**
257      * Given an index in the range <code>0...size()-1</code>, sets a new
258      * value for the <code>index</code>th key-value mapping that this
259      * SparseArray stores.
260      */
261     public void setValueAt(int index, E value) {
262         if (mGarbage) {
263             gc();
264         }
265         mValues[index] = value;
266     }
267     /**
268      * Returns the index for which {@link #keyAt} would return the
269      * specified key, or a negative number if the specified
270      * key is not mapped.
271      */
272     public int indexOfKey(int key) {
273         if (mGarbage) {
274             gc();
275         }
276         return ContainerHelpers.binarySearch(mKeys, mSize, key);
277     }
278     /**
279      * Returns an index for which {@link #valueAt} would return the
280      * specified key, or a negative number if no keys map to the
281      * specified value.
282      * <p>Beware that this is a linear search, unlike lookups by key,
283      * and that multiple keys can map to the same value and this will
284      * find only one of them.
285      * <p>Note also that unlike most collections' {@code indexOf} methods,
286      * this method compares values using {@code ==} rather than {@code equals}.
287      */
288     public int indexOfValue(E value) {
289         if (mGarbage) {
290             gc();
291         }
292         for (int i = 0; i < mSize; i++)
293             if (mValues[i] == value)
294                 return i;
295         return -1;
296     }
297     /**
298      * Removes all key-value mappings from this SparseArray.
299      */
300     public void clear() {
301         int n = mSize;
302         Object[] values = mValues;
303         for (int i = 0; i < n; i++) {
304             values[i] = null;
305         }
306         mSize = 0;
307         mGarbage = false;
308     }
309     /**
310      * Puts a key/value pair into the array, optimizing for the case where
311      * the key is greater than all existing keys in the array.
312      */
313     public void append(int key, E value) {
314         if (mSize != 0 && key <= mKeys[mSize - 1]) {
315             put(key, value);
316             return;
317         }
318         if (mGarbage && mSize >= mKeys.length) {
319             gc();
320         }
321         mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
322         mValues = GrowingArrayUtils.append(mValues, mSize, value);
323         mSize++;
324     }
325     /**
326      * {@inheritDoc}
327      *
328      * <p>This implementation composes a string by iterating over its mappings. If
329      * this map contains itself as a value, the string "(this Map)"
330      * will appear in its place.
331      */
332     @Override
333     public String toString() {
334         if (size() <= 0) {
335             return "{}";
336         }
337         StringBuilder buffer = new StringBuilder(mSize * 28);
338         buffer.append('{');
339         for (int i=0; i<mSize; i++) {
340             if (i > 0) {
341                 buffer.append(", ");
342             }
343             int key = keyAt(i);
344             buffer.append(key);
345             buffer.append('=');
346             Object value = valueAt(i);
347             if (value != this) {
348                 buffer.append(value);
349             } else {
350                 buffer.append("(this Map)");
351             }
352         }
353         buffer.append('}');
354         return buffer.toString();
355     }
356 }
SparseArray

SparseArray是Android框架独有的类,在标准的JDK中不存在这个类。它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray不需要对key和value进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。是骡子是马得拉出来遛遛,下面我们就通过几段程序来证明SparseArray在各方面表现如何,下面的试验结果时在我的Hike X1(Android 4.2.2)手机上运行得出的。

代码1:

int MAX = 100000;
long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
    hash.put(i, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;

代码2:

int MAX = 100000;
long start = System.currentTimeMillis();
SparseArray<String> sparse = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
    sparse.put(i, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;

我们分别在long start处和long ts处设置断点,然后通过DDMS工具查看内存使用情况。

代码1中,我们使用HashMap来创建100000条数据,开始创建前的系统内存情况为: 

创建HashMap之后,应用内存情况为: 可见创建HashMap用去约 13.2M内存。

再看 代码2,同样是创建100000条数据,我们用SparseArray来试试,开始创建前的内存使用情况为: 

创建SparseArray之后的情况: 创建SparseArray共用去 8.626M内存。

可见使用 SparseArray 的确比 HashMap 节省内存,大概节省 35%左右的内存。


我们再比较一下插入数据的效率如何,我们在加两段代码(主要就是把插入顺序变换一下,从大到小插入):

代码3:

int MAX = 100000;
long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
    hash.put(MAX - i -1, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;

代码4:

int MAX = 100000;
long start = System.currentTimeMillis();
SparseArray<String> sparse = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
    sparse.put(MAX - i -1, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;

我们分别把这4代码分别运行5次,对比一下ts的时间(单位毫秒):

#代码1代码2代码3代码4
1 10750ms 7429ms 10862ms 90527ms
2 10718ms 7386ms 10711ms 87990ms
3 10816ms 7462ms 11033ms 88259ms
4 10943ms 7386ms 10854ms 88474ms
5 10671ms 7317ms 10786ms 90630ms

通过结果我们看出,在正序插入数据时候,SparseArray比HashMap要快一些;HashMap不管是倒序还是正序开销几乎是一样的;但是SparseArray的倒序插入要比正序插入要慢10倍以上,这时为什么呢?我们再看下面一段代码:

代码5:

SparseArray<String> sparse = new SparseArray<String>(3);

sparse.put(1, "s1");
sparse.put(3, "s3");
sparse.put(2, "s2");

我们在Eclipse的debug模式中,看Variables窗口,如图: 

及时我们是按照1,3,2的顺序排列的,但是在SparseArray内部还是按照正序排列的,这时因为SparseArray在检索数据的时候使用的是二分查找,所以每次插入新数据的时候SparseArray都需要重新排序,所以代码4中,逆序是最差情况。


下面我们在简单看下检索情况:

代码5:

long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    hash.get(33333); //针对固定值检索
}
long end4search = System.currentTimeMillis() - start4search;

代码6:

long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    hash.get(i); //顺序检索
}
long end4search = System.currentTimeMillis() - start4search;

代码7:

long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    sparse.get(33333); //针对固定值检索
}
long end4search = System.currentTimeMillis() - start4search;

代码8:

long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    sparse.get(i); //顺序检索
}
long end4search = System.currentTimeMillis() - start4search;

表1:

#代码5代码6代码7代码8
1 4072ms 4318ms 3442ms 3390ms
2 4349ms 4536ms 3402ms 3420ms
3 4599ms 4203ms 3472ms 3376ms
4 4149ms 4086ms 3429ms 3786ms
5 4207ms 4219ms 3439ms 3376ms

代码9,我们试一些离散的数据。

//使用Foo为了避免由原始类型被自动封装(auto-boxing,比如把int类型自动转存Integer对象类型)造成的干扰。
class FOO{
    Integer objKey;
    int intKey;
}
...
int MAX = 100000;

HashMap<Integer, String> hash = new HashMap<Integer, String>();
SparseArray<String> sparse = new SparseArray<String>();

for (int i = 0; i < MAX; i++) {
    hash.put(i, String.valueOf(i));
    sparse.put(i, String.valueOf(i));
}

List<FOO> keylist4search = new ArrayList<FOO>();
for (int i = 0; i < MAX; i++) {
    FOO f = new FOO();
    f.intKey = i;
    f.objKey = Integer.valueOf(i);
    keylist4search.add(f);
}

long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    hash.get(keylist4search.get(i).objKey);
}
long end4searchHash = System.currentTimeMillis() - start4search;

long start4search2 = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
    sparse.get(keylist4search.get(i).intKey);
}
long end4searchSparse = System.currentTimeMillis() - start4search2;

代码9,运行5次之后的结果如下:

表2:

#end4searchHashend4searchSparse
1 2402ms 4577ms
2 2249ms 4188ms
3 2649ms 4821ms
4 2404ms 4598ms
5 2413ms 4547ms

从上面两个表中我们可以看出,当SparseArray中存在需要检索的下标时,SparseArray的性能略胜一筹(表1)。但是当检索的下标比较离散时,SparseArray需要使用多次二分检索,性能显然比hash检索方式要慢一些了(表2),但是按照官方文档的说法性能差异不是很大,不超过50%( For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.)

总体而言,在Android这种内存比CPU更金贵的系统中,能经济地使用内存还是上策,何况SparseArray在其他方面的表现也不算差(另外,SparseArray删除数据的时候也做了优化——使用了延迟整理数组的方法,可参考官方文档SparseArray,读者可以自行把代码9中的hash.getsparse.get改成hash.removesparse.delete试试,你会发现二者的性能相差无几)。而且,使用SparseArray代替HashMap<integer,e>也是官方推荐的做法,在Eclipse中也会提示你优先使用SparseArray,如图: 

另外,我们还可以用 LongSparseArray来替代HashMap<long,e>。SparseBooleanArray来替代HashMap<integer,boolean>。

=========================================================================================

HashMap是java里比较常用的一个集合类,我比较习惯用来缓存一些处理后的结果。最近在做一个Android项目,在代码中定义这样一个变量,实例化时,Eclipse却给出了一个 performance 警告。

意思就是说用SparseArray<E>来替代,以获取更好性能。老实说,对SparseArray并不熟悉,第一感觉应该是Android提供的一个类。按住Ctrl点击进入SparseArray的源码,果不其然,确定是Android提供的一个工具类。

单纯从字面上来理解,SparseArray指的是稀疏数组(Sparse array),所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。

假设有一个9*7的数组,其内容如下:

在此数组中,共有63个空间,但却只使用了5个元素,造成58个元素空间的浪费。以下我们就使用稀疏数组重新来定义这个数组:

其中在稀疏数组中第一部分所记录的是原数组的列数和行数以及元素使用的个数、第二部分所记录的是原数组中元素的位置和内容。经过压缩之后,原来需要声明大小为63的数组,而使用压缩后,只需要声明大小为6*3的数组,仅需18个存储空间。

继续阅读SparseArray的源码,从构造方法我们可以看出,它和一般的List一样,可以预先设置容器大小,默认的大小是10:

   
    public SparseArray() {
        this(10);
    }

    public SparseArray(int initialCapacity) {
        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);

        mKeys = new int[initialCapacity];
        mValues = new Object[initialCapacity];
        mSize = 0;
    }

再来看看它对数据的“增删改查”。

它有两个方法可以添加键值对:

 public void put(int key, E value) {}
 public void append(int key, E value){}

有四个方法可以执行删除操作:

 public void delete(int key) {}
 public void remove(int key) {} //直接调用的delete(int key)
 public void removeAt(int index){}
 public void clear(){}

修改数据起初以为只有setValueAt(int index, E value)可以修改数据,但后来发现put(int key, E value)也可以修改数据,我们查看put(int key, E value)的源码可知,在put数据之前,会先查找要put的数据是否已经存在,如果存在就是修改,不存在就添加。

    public void put(int key, E value) {
        int i = binarySearch(mKeys, 0, mSize, key);

        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;

            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            if (mGarbage && mSize >= mKeys.length) {
                gc();

                // Search again because indices may have changed.
                i = ~binarySearch(mKeys, 0, mSize, key);
            }
            …………

所以,修改数据实际也有两种方法:

 public void put(int key, E value)
 public void setValueAt(int index, E value)

最后再来看看如何查找数据。有两个方法可以查询取值:

 public E get(int key)
 public E get(int key, E valueIfKeyNotFound)

其中get(int key)也只是调用了 get(int key,E valueIfKeyNotFound),最后一个从传参的变量名就能看出,传入的是找不到的时候返回的值.get(int key)当找不到的时候,默认返回null。

查看第几个位置的键:

 public int keyAt(int index)

有一点需要注意的是,查看键所在位置,由于是采用二分法查找键的位置,所以找不到时返回小于0的数值,而不是返回-1。返回的负值是表示它在找不到时所在的位置。

查看第几个位置的值:

 public E valueAt(int index)

查看值所在位置,没有的话返回-1:

 public int indexOfValue(E value)

最后,发现其核心就是折半查找函数(binarySearch),算法设计的很不错。

    private static int binarySearch(int[] a, int start, int len, int key) {
        int high = start + len, low = start - 1, guess;

        while (high - low > 1) {
            guess = (high + low) / 2;

            if (a[guess] < key)
                low = guess;
            else
                high = guess;
        }

        if (high == start + len)
            return ~(start + len);
        else if (a[high] == key)
            return high;
        else
            return ~high;
    }

相应的也有SparseBooleanArray,用来取代HashMap<Integer, Boolean>,SparseIntArray用来取代HashMap<Integer, Integer>,大家有兴趣的可以研究。

总结:SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)。在Android中,当我们需要定义

HashMap<Integer, E> hashMap = new HashMap<Integer, E>();

时,我们可以使用如下的方式来取得更好的性能.

SparseArray<E> sparseArray = new SparseArray<E>();


关于ArrayMap与HashMap性能比较:
这里先附上ArrayMap源码:
  1 /*
  2  * Copyright (C) 2013 The Android Open Source Project
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 package android.util;
 17 import libcore.util.EmptyArray;
 18 import java.util.Collection;
 19 import java.util.Map;
 20 import java.util.Set;
 21 /**
 22  * ArrayMap is a generic key->value mapping data structure that is
 23  * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
 24  * It keeps its mappings in an array data structure -- an integer array of hash
 25  * codes for each item, and an Object array of the key/value pairs.  This allows it to
 26  * avoid having to create an extra object for every entry put in to the map, and it
 27  * also tries to control the growth of the size of these arrays more aggressively
 28  * (since growing them only requires copying the entries in the array, not rebuilding
 29  * a hash map).
 30  *
 31  * <p>Note that this implementation is not intended to be appropriate for data structures
 32  * that may contain large numbers of items.  It is generally slower than a traditional
 33  * HashMap, since lookups require a binary search and adds and removes require inserting
 34  * and deleting entries in the array.  For containers holding up to hundreds of items,
 35  * the performance difference is not significant, less than 50%.</p>
 36  *
 37  * <p>Because this container is intended to better balance memory use, unlike most other
 38  * standard Java containers it will shrink its array as items are removed from it.  Currently
 39  * you have no control over this shrinking -- if you set a capacity and then remove an
 40  * item, it may reduce the capacity to better match the current size.  In the future an
 41  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
 42  */
 43 public final class ArrayMap<K, V> implements Map<K, V> {
 44     private static final boolean DEBUG = false;
 45     private static final String TAG = "ArrayMap";
 46     /**
 47      * The minimum amount by which the capacity of a ArrayMap will increase.
 48      * This is tuned to be relatively space-efficient.
 49      */
 50     private static final int BASE_SIZE = 4;
 51     /**
 52      * Maximum number of entries to have in array caches.
 53      */
 54     private static final int CACHE_SIZE = 10;
 55     /**
 56      * @hide Special immutable empty ArrayMap.
 57      */
 58     public static final ArrayMap EMPTY = new ArrayMap(true);
 59     /**
 60      * Caches of small array objects to avoid spamming garbage.  The cache
 61      * Object[] variable is a pointer to a linked list of array objects.
 62      * The first entry in the array is a pointer to the next array in the
 63      * list; the second entry is a pointer to the int[] hash code array for it.
 64      */
 65     static Object[] mBaseCache;
 66     static int mBaseCacheSize;
 67     static Object[] mTwiceBaseCache;
 68     static int mTwiceBaseCacheSize;
 69     /**
 70      * Special hash array value that indicates the container is immutable.
 71      */
 72     static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
 73     int[] mHashes;
 74     Object[] mArray;
 75     int mSize;
 76     MapCollections<K, V> mCollections;
 77     int indexOf(Object key, int hash) {
 78         final int N = mSize;
 79         // Important fast case: if nothing is in here, nothing to look for.
 80         if (N == 0) {
 81             return ~0;
 82         }
 83         int index = ContainerHelpers.binarySearch(mHashes, N, hash);
 84         // If the hash code wasn't found, then we have no entry for this key.
 85         if (index < 0) {
 86             return index;
 87         }
 88         // If the key at the returned index matches, that's what we want.
 89         if (key.equals(mArray[index<<1])) {
 90             return index;
 91         }
 92         // Search for a matching key after the index.
 93         int end;
 94         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
 95             if (key.equals(mArray[end << 1])) return end;
 96         }
 97         // Search for a matching key before the index.
 98         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
 99             if (key.equals(mArray[i << 1])) return i;
100         }
101         // Key not found -- return negative value indicating where a
102         // new entry for this key should go.  We use the end of the
103         // hash chain to reduce the number of array entries that will
104         // need to be copied when inserting.
105         return ~end;
106     }
107     int indexOfNull() {
108         final int N = mSize;
109         // Important fast case: if nothing is in here, nothing to look for.
110         if (N == 0) {
111             return ~0;
112         }
113         int index = ContainerHelpers.binarySearch(mHashes, N, 0);
114         // If the hash code wasn't found, then we have no entry for this key.
115         if (index < 0) {
116             return index;
117         }
118         // If the key at the returned index matches, that's what we want.
119         if (null == mArray[index<<1]) {
120             return index;
121         }
122         // Search for a matching key after the index.
123         int end;
124         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
125             if (null == mArray[end << 1]) return end;
126         }
127         // Search for a matching key before the index.
128         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
129             if (null == mArray[i << 1]) return i;
130         }
131         // Key not found -- return negative value indicating where a
132         // new entry for this key should go.  We use the end of the
133         // hash chain to reduce the number of array entries that will
134         // need to be copied when inserting.
135         return ~end;
136     }
137     private void allocArrays(final int size) {
138         if (mHashes == EMPTY_IMMUTABLE_INTS) {
139             throw new UnsupportedOperationException("ArrayMap is immutable");
140         }
141         if (size == (BASE_SIZE*2)) {
142             synchronized (ArrayMap.class) {
143                 if (mTwiceBaseCache != null) {
144                     final Object[] array = mTwiceBaseCache;
145                     mArray = array;
146                     mTwiceBaseCache = (Object[])array[0];
147                     mHashes = (int[])array[1];
148                     array[0] = array[1] = null;
149                     mTwiceBaseCacheSize--;
150                     if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
151                             + " now have " + mTwiceBaseCacheSize + " entries");
152                     return;
153                 }
154             }
155         } else if (size == BASE_SIZE) {
156             synchronized (ArrayMap.class) {
157                 if (mBaseCache != null) {
158                     final Object[] array = mBaseCache;
159                     mArray = array;
160                     mBaseCache = (Object[])array[0];
161                     mHashes = (int[])array[1];
162                     array[0] = array[1] = null;
163                     mBaseCacheSize--;
164                     if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
165                             + " now have " + mBaseCacheSize + " entries");
166                     return;
167                 }
168             }
169         }
170         mHashes = new int[size];
171         mArray = new Object[size<<1];
172     }
173     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
174         if (hashes.length == (BASE_SIZE*2)) {
175             synchronized (ArrayMap.class) {
176                 if (mTwiceBaseCacheSize < CACHE_SIZE) {
177                     array[0] = mTwiceBaseCache;
178                     array[1] = hashes;
179                     for (int i=(size<<1)-1; i>=2; i--) {
180                         array[i] = null;
181                     }
182                     mTwiceBaseCache = array;
183                     mTwiceBaseCacheSize++;
184                     if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
185                             + " now have " + mTwiceBaseCacheSize + " entries");
186                 }
187             }
188         } else if (hashes.length == BASE_SIZE) {
189             synchronized (ArrayMap.class) {
190                 if (mBaseCacheSize < CACHE_SIZE) {
191                     array[0] = mBaseCache;
192                     array[1] = hashes;
193                     for (int i=(size<<1)-1; i>=2; i--) {
194                         array[i] = null;
195                     }
196                     mBaseCache = array;
197                     mBaseCacheSize++;
198                     if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
199                             + " now have " + mBaseCacheSize + " entries");
200                 }
201             }
202         }
203     }
204     /**
205      * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
206      * will grow once items are added to it.
207      */
208     public ArrayMap() {
209         mHashes = EmptyArray.INT;
210         mArray = EmptyArray.OBJECT;
211         mSize = 0;
212     }
213     /**
214      * Create a new ArrayMap with a given initial capacity.
215      */
216     public ArrayMap(int capacity) {
217         if (capacity == 0) {
218             mHashes = EmptyArray.INT;
219             mArray = EmptyArray.OBJECT;
220         } else {
221             allocArrays(capacity);
222         }
223         mSize = 0;
224     }
225     private ArrayMap(boolean immutable) {
226         // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
227         // instance instead of the usual EmptyArray.INT. The reference
228         // is checked later to see if the array is allowed to grow.
229         mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
230         mArray = EmptyArray.OBJECT;
231         mSize = 0;
232     }
233     /**
234      * Create a new ArrayMap with the mappings from the given ArrayMap.
235      */
236     public ArrayMap(ArrayMap<K, V> map) {
237         this();
238         if (map != null) {
239             putAll(map);
240         }
241     }
242     /**
243      * Make the array map empty.  All storage is released.
244      */
245     @Override
246     public void clear() {
247         if (mSize > 0) {
248             freeArrays(mHashes, mArray, mSize);
249             mHashes = EmptyArray.INT;
250             mArray = EmptyArray.OBJECT;
251             mSize = 0;
252         }
253     }
254     /**
255      * @hide
256      * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
257      */
258     public void erase() {
259         if (mSize > 0) {
260             final int N = mSize<<1;
261             final Object[] array = mArray;
262             for (int i=0; i<N; i++) {
263                 array[i] = null;
264             }
265             mSize = 0;
266         }
267     }
268     /**
269      * Ensure the array map can hold at least <var>minimumCapacity</var>
270      * items.
271      */
272     public void ensureCapacity(int minimumCapacity) {
273         if (mHashes.length < minimumCapacity) {
274             final int[] ohashes = mHashes;
275             final Object[] oarray = mArray;
276             allocArrays(minimumCapacity);
277             if (mSize > 0) {
278                 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
279                 System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
280             }
281             freeArrays(ohashes, oarray, mSize);
282         }
283     }
284     /**
285      * Check whether a key exists in the array.
286      *
287      * @param key The key to search for.
288      * @return Returns true if the key exists, else false.
289      */
290     @Override
291     public boolean containsKey(Object key) {
292         return indexOfKey(key) >= 0;
293     }
294     /**
295      * Returns the index of a key in the set.
296      *
297      * @param key The key to search for.
298      * @return Returns the index of the key if it exists, else a negative integer.
299      */
300     public int indexOfKey(Object key) {
301         return key == null ? indexOfNull() : indexOf(key, key.hashCode());
302     }
303     int indexOfValue(Object value) {
304         final int N = mSize*2;
305         final Object[] array = mArray;
306         if (value == null) {
307             for (int i=1; i<N; i+=2) {
308                 if (array[i] == null) {
309                     return i>>1;
310                 }
311             }
312         } else {
313             for (int i=1; i<N; i+=2) {
314                 if (value.equals(array[i])) {
315                     return i>>1;
316                 }
317             }
318         }
319         return -1;
320     }
321     /**
322      * Check whether a value exists in the array.  This requires a linear search
323      * through the entire array.
324      *
325      * @param value The value to search for.
326      * @return Returns true if the value exists, else false.
327      */
328     @Override
329     public boolean containsValue(Object value) {
330         return indexOfValue(value) >= 0;
331     }
332     /**
333      * Retrieve a value from the array.
334      * @param key The key of the value to retrieve.
335      * @return Returns the value associated with the given key,
336      * or null if there is no such key.
337      */
338     @Override
339     public V get(Object key) {
340         final int index = indexOfKey(key);
341         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
342     }
343     /**
344      * Return the key at the given index in the array.
345      * @param index The desired index, must be between 0 and {@link #size()}-1.
346      * @return Returns the key stored at the given index.
347      */
348     public K keyAt(int index) {
349         return (K)mArray[index << 1];
350     }
351     /**
352      * Return the value at the given index in the array.
353      * @param index The desired index, must be between 0 and {@link #size()}-1.
354      * @return Returns the value stored at the given index.
355      */
356     public V valueAt(int index) {
357         return (V)mArray[(index << 1) + 1];
358     }
359     /**
360      * Set the value at a given index in the array.
361      * @param index The desired index, must be between 0 and {@link #size()}-1.
362      * @param value The new value to store at this index.
363      * @return Returns the previous value at the given index.
364      */
365     public V setValueAt(int index, V value) {
366         index = (index << 1) + 1;
367         V old = (V)mArray[index];
368         mArray[index] = value;
369         return old;
370     }
371     /**
372      * Return true if the array map contains no items.
373      */
374     @Override
375     public boolean isEmpty() {
376         return mSize <= 0;
377     }
378     /**
379      * Add a new value to the array map.
380      * @param key The key under which to store the value.  If
381      * this key already exists in the array, its value will be replaced.
382      * @param value The value to store for the given key.
383      * @return Returns the old value that was stored for the given key, or null if there
384      * was no such key.
385      */
386     @Override
387     public V put(K key, V value) {
388         final int hash;
389         int index;
390         if (key == null) {
391             hash = 0;
392             index = indexOfNull();
393         } else {
394             hash = key.hashCode();
395             index = indexOf(key, hash);
396         }
397         if (index >= 0) {
398             index = (index<<1) + 1;
399             final V old = (V)mArray[index];
400             mArray[index] = value;
401             return old;
402         }
403         index = ~index;
404         if (mSize >= mHashes.length) {
405             final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
406                     : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
407             if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
408             final int[] ohashes = mHashes;
409             final Object[] oarray = mArray;
410             allocArrays(n);
411             if (mHashes.length > 0) {
412                 if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
413                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
414                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
415             }
416             freeArrays(ohashes, oarray, mSize);
417         }
418         if (index < mSize) {
419             if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
420                     + " to " + (index+1));
421             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
422             System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
423         }
424         mHashes[index] = hash;
425         mArray[index<<1] = key;
426         mArray[(index<<1)+1] = value;
427         mSize++;
428         return null;
429     }
430     /**
431      * Special fast path for appending items to the end of the array without validation.
432      * The array must already be large enough to contain the item.
433      * @hide
434      */
435     public void append(K key, V value) {
436         int index = mSize;
437         final int hash = key == null ? 0 : key.hashCode();
438         if (index >= mHashes.length) {
439             throw new IllegalStateException("Array is full");
440         }
441         if (index > 0 && mHashes[index-1] > hash) {
442             RuntimeException e = new RuntimeException("here");
443             e.fillInStackTrace();
444             Log.w(TAG, "New hash " + hash
445                     + " is before end of array hash " + mHashes[index-1]
446                     + " at index " + index + " key " + key, e);
447             put(key, value);
448             return;
449         }
450         mSize = index+1;
451         mHashes[index] = hash;
452         index <<= 1;
453         mArray[index] = key;
454         mArray[index+1] = value;
455     }
456     /**
457      * The use of the {@link #append} function can result in invalid array maps, in particular
458      * an array map where the same key appears multiple times.  This function verifies that
459      * the array map is valid, throwing IllegalArgumentException if a problem is found.  The
460      * main use for this method is validating an array map after unpacking from an IPC, to
461      * protect against malicious callers.
462      * @hide
463      */
464     public void validate() {
465         final int N = mSize;
466         if (N <= 1) {
467             // There can't be dups.
468             return;
469         }
470         int basehash = mHashes[0];
471         int basei = 0;
472         for (int i=1; i<N; i++) {
473             int hash = mHashes[i];
474             if (hash != basehash) {
475                 basehash = hash;
476                 basei = i;
477                 continue;
478             }
479             // We are in a run of entries with the same hash code.  Go backwards through
480             // the array to see if any keys are the same.
481             final Object cur = mArray[i<<1];
482             for (int j=i-1; j>=basei; j--) {
483                 final Object prev = mArray[j<<1];
484                 if (cur == prev) {
485                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
486                 }
487                 if (cur != null && prev != null && cur.equals(prev)) {
488                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
489                 }
490             }
491         }
492     }
493     /**
494      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
495      * @param array The array whose contents are to be retrieved.
496      */
497     public void putAll(ArrayMap<? extends K, ? extends V> array) {
498         final int N = array.mSize;
499         ensureCapacity(mSize + N);
500         if (mSize == 0) {
501             if (N > 0) {
502                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
503                 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
504                 mSize = N;
505             }
506         } else {
507             for (int i=0; i<N; i++) {
508                 put(array.keyAt(i), array.valueAt(i));
509             }
510         }
511     }
512     /**
513      * Remove an existing key from the array map.
514      * @param key The key of the mapping to remove.
515      * @return Returns the value that was stored under the key, or null if there
516      * was no such key.
517      */
518     @Override
519     public V remove(Object key) {
520         final int index = indexOfKey(key);
521         if (index >= 0) {
522             return removeAt(index);
523         }
524         return null;
525     }
526     /**
527      * Remove the key/value mapping at the given index.
528      * @param index The desired index, must be between 0 and {@link #size()}-1.
529      * @return Returns the value that was stored at this index.
530      */
531     public V removeAt(int index) {
532         final Object old = mArray[(index << 1) + 1];
533         if (mSize <= 1) {
534             // Now empty.
535             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
536             freeArrays(mHashes, mArray, mSize);
537             mHashes = EmptyArray.INT;
538             mArray = EmptyArray.OBJECT;
539             mSize = 0;
540         } else {
541             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
542                 // Shrunk enough to reduce size of arrays.  We don't allow it to
543                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
544                 // that and BASE_SIZE.
545                 final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
546                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
547                 final int[] ohashes = mHashes;
548                 final Object[] oarray = mArray;
549                 allocArrays(n);
550                 mSize--;
551                 if (index > 0) {
552                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
553                     System.arraycopy(ohashes, 0, mHashes, 0, index);
554                     System.arraycopy(oarray, 0, mArray, 0, index << 1);
555                 }
556                 if (index < mSize) {
557                     if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
558                             + " to " + index);
559                     System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
560                     System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
561                             (mSize - index) << 1);
562                 }
563             } else {
564                 mSize--;
565                 if (index < mSize) {
566                     if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
567                             + " to " + index);
568                     System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
569                     System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
570                             (mSize - index) << 1);
571                 }
572                 mArray[mSize << 1] = null;
573                 mArray[(mSize << 1) + 1] = null;
574             }
575         }
576         return (V)old;
577     }
578     /**
579      * Return the number of items in this array map.
580      */
581     @Override
582     public int size() {
583         return mSize;
584     }
585     /**
586      * {@inheritDoc}
587      *
588      * <p>This implementation returns false if the object is not a map, or
589      * if the maps have different sizes. Otherwise, for each key in this map,
590      * values of both maps are compared. If the values for any key are not
591      * equal, the method returns false, otherwise it returns true.
592      */
593     @Override
594     public boolean equals(Object object) {
595         if (this == object) {
596             return true;
597         }
598         if (object instanceof Map) {
599             Map<?, ?> map = (Map<?, ?>) object;
600             if (size() != map.size()) {
601                 return false;
602             }
603             try {
604                 for (int i=0; i<mSize; i++) {
605                     K key = keyAt(i);
606                     V mine = valueAt(i);
607                     Object theirs = map.get(key);
608                     if (mine == null) {
609                         if (theirs != null || !map.containsKey(key)) {
610                             return false;
611                         }
612                     } else if (!mine.equals(theirs)) {
613                         return false;
614                     }
615                 }
616             } catch (NullPointerException ignored) {
617                 return false;
618             } catch (ClassCastException ignored) {
619                 return false;
620             }
621             return true;
622         }
623         return false;
624     }
625     /**
626      * {@inheritDoc}
627      */
628     @Override
629     public int hashCode() {
630         final int[] hashes = mHashes;
631         final Object[] array = mArray;
632         int result = 0;
633         for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
634             Object value = array[v];
635             result += hashes[i] ^ (value == null ? 0 : value.hashCode());
636         }
637         return result;
638     }
639     /**
640      * {@inheritDoc}
641      *
642      * <p>This implementation composes a string by iterating over its mappings. If
643      * this map contains itself as a key or a value, the string "(this Map)"
644      * will appear in its place.
645      */
646     @Override
647     public String toString() {
648         if (isEmpty()) {
649             return "{}";
650         }
651         StringBuilder buffer = new StringBuilder(mSize * 28);
652         buffer.append('{');
653         for (int i=0; i<mSize; i++) {
654             if (i > 0) {
655                 buffer.append(", ");
656             }
657             Object key = keyAt(i);
658             if (key != this) {
659                 buffer.append(key);
660             } else {
661                 buffer.append("(this Map)");
662             }
663             buffer.append('=');
664             Object value = valueAt(i);
665             if (value != this) {
666                 buffer.append(value);
667             } else {
668                 buffer.append("(this Map)");
669             }
670         }
671         buffer.append('}');
672         return buffer.toString();
673     }
674     // ------------------------------------------------------------------------
675     // Interop with traditional Java containers.  Not as efficient as using
676     // specialized collection APIs.
677     // ------------------------------------------------------------------------
678     private MapCollections<K, V> getCollection() {
679         if (mCollections == null) {
680             mCollections = new MapCollections<K, V>() {
681                 @Override
682                 protected int colGetSize() {
683                     return mSize;
684                 }
685                 @Override
686                 protected Object colGetEntry(int index, int offset) {
687                     return mArray[(index<<1) + offset];
688                 }
689                 @Override
690                 protected int colIndexOfKey(Object key) {
691                     return indexOfKey(key);
692                 }
693                 @Override
694                 protected int colIndexOfValue(Object value) {
695                     return indexOfValue(value);
696                 }
697                 @Override
698                 protected Map<K, V> colGetMap() {
699                     return ArrayMap.this;
700                 }
701                 @Override
702                 protected void colPut(K key, V value) {
703                     put(key, value);
704                 }
705                 @Override
706                 protected V colSetValue(int index, V value) {
707                     return setValueAt(index, value);
708                 }
709                 @Override
710                 protected void colRemoveAt(int index) {
711                     removeAt(index);
712                 }
713                 @Override
714                 protected void colClear() {
715                     clear();
716                 }
717             };
718         }
719         return mCollections;
720     }
721     /**
722      * Determine if the array map contains all of the keys in the given collection.
723      * @param collection The collection whose contents are to be checked against.
724      * @return Returns true if this array map contains a key for every entry
725      * in <var>collection</var>, else returns false.
726      */
727     public boolean containsAll(Collection<?> collection) {
728         return MapCollections.containsAllHelper(this, collection);
729     }
730     /**
731      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
732      * @param map The map whose contents are to be retrieved.
733      */
734     @Override
735     public void putAll(Map<? extends K, ? extends V> map) {
736         ensureCapacity(mSize + map.size());
737         for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
738             put(entry.getKey(), entry.getValue());
739         }
740     }
741     /**
742      * Remove all keys in the array map that exist in the given collection.
743      * @param collection The collection whose contents are to be used to remove keys.
744      * @return Returns true if any keys were removed from the array map, else false.
745      */
746     public boolean removeAll(Collection<?> collection) {
747         return MapCollections.removeAllHelper(this, collection);
748     }
749     /**
750      * Remove all keys in the array map that do <b>not</b> exist in the given collection.
751      * @param collection The collection whose contents are to be used to determine which
752      * keys to keep.
753      * @return Returns true if any keys were removed from the array map, else false.
754      */
755     public boolean retainAll(Collection<?> collection) {
756         return MapCollections.retainAllHelper(this, collection);
757     }
758     /**
759      * Return a {@link java.util.Set} for iterating over and interacting with all mappings
760      * in the array map.
761      *
762      * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
763      * requires generating a number of temporary objects and allocates additional state
764      * information associated with the container that will remain for the life of the container.</p>
765      *
766      * <p><b>Note:</b></p> the semantics of this
767      * Set are subtly different than that of a {@link java.util.HashMap}: most important,
768      * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
769      * object that exists for the entire iterator, so you can <b>not</b> hold on to it
770      * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
771      */
772     @Override
773     public Set<Map.Entry<K, V>> entrySet() {
774         return getCollection().getEntrySet();
775     }
776     /**
777      * Return a {@link java.util.Set} for iterating over and interacting with all keys
778      * in the array map.
779      *
780      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
781      * requires generating a number of temporary objects and allocates additional state
782      * information associated with the container that will remain for the life of the container.</p>
783      */
784     @Override
785     public Set<K> keySet() {
786         return getCollection().getKeySet();
787     }
788     /**
789      * Return a {@link java.util.Collection} for iterating over and interacting with all values
790      * in the array map.
791      *
792      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
793      * requires generating a number of temporary objects and allocates additional state
794      * information associated with the container that will remain for the life of the container.</p>
795      */
796     @Override
797     public Collection<V> values() {
798         return getCollection().getValues();
799     }
800 }
ArrayMap
 
原文地址:https://www.cnblogs.com/CoolRandy/p/4547904.html