Home | History | Annotate | Download | only in util
      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 
     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 
    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 
    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 
    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      */
    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      */
    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 
    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      */
    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
    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      */
    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      */
    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
    323     public boolean containsKey(Object key) {
    324         return key == null ? (indexOfNull() >= 0) : (indexOf(key, key.hashCode()) >= 0);
    325     }
    326 
    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
    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
    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      */
    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      */
    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      */
    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
    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
    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      */
    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      */
    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
    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      */
    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
    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
    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
    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
    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 
    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      */
    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
    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      */
    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      */
    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
    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
    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
    826     public Collection<V> values() {
    827         return getCollection().getValues();
    828     }
    829 }
    830