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