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 android.annotation.UnsupportedAppUsage;
     20 
     21 import com.android.internal.util.ArrayUtils;
     22 
     23 import libcore.util.EmptyArray;
     24 
     25 import java.util.Collection;
     26 import java.util.ConcurrentModificationException;
     27 import java.util.Map;
     28 import java.util.Set;
     29 
     30 /**
     31  * ArrayMap is a generic key->value mapping data structure that is
     32  * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
     33  * It keeps its mappings in an array data structure -- an integer array of hash
     34  * codes for each item, and an Object array of the key/value pairs.  This allows it to
     35  * avoid having to create an extra object for every entry put in to the map, and it
     36  * also tries to control the growth of the size of these arrays more aggressively
     37  * (since growing them only requires copying the entries in the array, not rebuilding
     38  * a hash map).
     39  *
     40  * <p>Note that this implementation is not intended to be appropriate for data structures
     41  * that may contain large numbers of items.  It is generally slower than a traditional
     42  * HashMap, since lookups require a binary search and adds and removes require inserting
     43  * and deleting entries in the array.  For containers holding up to hundreds of items,
     44  * the performance difference is not significant, less than 50%.</p>
     45  *
     46  * <p>Because this container is intended to better balance memory use, unlike most other
     47  * standard Java containers it will shrink its array as items are removed from it.  Currently
     48  * you have no control over this shrinking -- if you set a capacity and then remove an
     49  * item, it may reduce the capacity to better match the current size.  In the future an
     50  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
     51  */
     52 public final class ArrayMap<K, V> implements Map<K, V> {
     53     private static final boolean DEBUG = false;
     54     private static final String TAG = "ArrayMap";
     55 
     56     /**
     57      * Attempt to spot concurrent modifications to this data structure.
     58      *
     59      * It's best-effort, but any time we can throw something more diagnostic than an
     60      * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to
     61      * save a lot of development time.
     62      *
     63      * Good times to look for CME include after any allocArrays() call and at the end of
     64      * functions that change mSize (put/remove/clear).
     65      */
     66     private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
     67 
     68     /**
     69      * The minimum amount by which the capacity of a ArrayMap will increase.
     70      * This is tuned to be relatively space-efficient.
     71      */
     72     private static final int BASE_SIZE = 4;
     73 
     74     /**
     75      * Maximum number of entries to have in array caches.
     76      */
     77     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     78     private static final int CACHE_SIZE = 10;
     79 
     80     /**
     81      * Special hash array value that indicates the container is immutable.
     82      */
     83     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     84     static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
     85 
     86     /**
     87      * @hide Special immutable empty ArrayMap.
     88      */
     89     @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map.
     90     public static final ArrayMap EMPTY = new ArrayMap<>(-1);
     91 
     92     /**
     93      * Caches of small array objects to avoid spamming garbage.  The cache
     94      * Object[] variable is a pointer to a linked list of array objects.
     95      * The first entry in the array is a pointer to the next array in the
     96      * list; the second entry is a pointer to the int[] hash code array for it.
     97      */
     98     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
     99     static Object[] mBaseCache;
    100     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    101     static int mBaseCacheSize;
    102     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    103     static Object[] mTwiceBaseCache;
    104     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    105     static int mTwiceBaseCacheSize;
    106 
    107     final boolean mIdentityHashCode;
    108     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
    109     int[] mHashes;
    110     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
    111     Object[] mArray;
    112     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
    113     int mSize;
    114     MapCollections<K, V> mCollections;
    115 
    116     private static int binarySearchHashes(int[] hashes, int N, int hash) {
    117         try {
    118             return ContainerHelpers.binarySearch(hashes, N, hash);
    119         } catch (ArrayIndexOutOfBoundsException e) {
    120             if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
    121                 throw new ConcurrentModificationException();
    122             } else {
    123                 throw e; // the cache is poisoned at this point, there's not much we can do
    124             }
    125         }
    126     }
    127 
    128     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
    129     int indexOf(Object key, int hash) {
    130         final int N = mSize;
    131 
    132         // Important fast case: if nothing is in here, nothing to look for.
    133         if (N == 0) {
    134             return ~0;
    135         }
    136 
    137         int index = binarySearchHashes(mHashes, N, hash);
    138 
    139         // If the hash code wasn't found, then we have no entry for this key.
    140         if (index < 0) {
    141             return index;
    142         }
    143 
    144         // If the key at the returned index matches, that's what we want.
    145         if (key.equals(mArray[index<<1])) {
    146             return index;
    147         }
    148 
    149         // Search for a matching key after the index.
    150         int end;
    151         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
    152             if (key.equals(mArray[end << 1])) return end;
    153         }
    154 
    155         // Search for a matching key before the index.
    156         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
    157             if (key.equals(mArray[i << 1])) return i;
    158         }
    159 
    160         // Key not found -- return negative value indicating where a
    161         // new entry for this key should go.  We use the end of the
    162         // hash chain to reduce the number of array entries that will
    163         // need to be copied when inserting.
    164         return ~end;
    165     }
    166 
    167     @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
    168     int indexOfNull() {
    169         final int N = mSize;
    170 
    171         // Important fast case: if nothing is in here, nothing to look for.
    172         if (N == 0) {
    173             return ~0;
    174         }
    175 
    176         int index = binarySearchHashes(mHashes, N, 0);
    177 
    178         // If the hash code wasn't found, then we have no entry for this key.
    179         if (index < 0) {
    180             return index;
    181         }
    182 
    183         // If the key at the returned index matches, that's what we want.
    184         if (null == mArray[index<<1]) {
    185             return index;
    186         }
    187 
    188         // Search for a matching key after the index.
    189         int end;
    190         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
    191             if (null == mArray[end << 1]) return end;
    192         }
    193 
    194         // Search for a matching key before the index.
    195         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
    196             if (null == mArray[i << 1]) return i;
    197         }
    198 
    199         // Key not found -- return negative value indicating where a
    200         // new entry for this key should go.  We use the end of the
    201         // hash chain to reduce the number of array entries that will
    202         // need to be copied when inserting.
    203         return ~end;
    204     }
    205 
    206     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    207     private void allocArrays(final int size) {
    208         if (mHashes == EMPTY_IMMUTABLE_INTS) {
    209             throw new UnsupportedOperationException("ArrayMap is immutable");
    210         }
    211         if (size == (BASE_SIZE*2)) {
    212             synchronized (ArrayMap.class) {
    213                 if (mTwiceBaseCache != null) {
    214                     final Object[] array = mTwiceBaseCache;
    215                     mArray = array;
    216                     mTwiceBaseCache = (Object[])array[0];
    217                     mHashes = (int[])array[1];
    218                     array[0] = array[1] = null;
    219                     mTwiceBaseCacheSize--;
    220                     if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
    221                             + " now have " + mTwiceBaseCacheSize + " entries");
    222                     return;
    223                 }
    224             }
    225         } else if (size == BASE_SIZE) {
    226             synchronized (ArrayMap.class) {
    227                 if (mBaseCache != null) {
    228                     final Object[] array = mBaseCache;
    229                     mArray = array;
    230                     mBaseCache = (Object[])array[0];
    231                     mHashes = (int[])array[1];
    232                     array[0] = array[1] = null;
    233                     mBaseCacheSize--;
    234                     if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
    235                             + " now have " + mBaseCacheSize + " entries");
    236                     return;
    237                 }
    238             }
    239         }
    240 
    241         mHashes = new int[size];
    242         mArray = new Object[size<<1];
    243     }
    244 
    245     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    246     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    247         if (hashes.length == (BASE_SIZE*2)) {
    248             synchronized (ArrayMap.class) {
    249                 if (mTwiceBaseCacheSize < CACHE_SIZE) {
    250                     array[0] = mTwiceBaseCache;
    251                     array[1] = hashes;
    252                     for (int i=(size<<1)-1; i>=2; i--) {
    253                         array[i] = null;
    254                     }
    255                     mTwiceBaseCache = array;
    256                     mTwiceBaseCacheSize++;
    257                     if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
    258                             + " now have " + mTwiceBaseCacheSize + " entries");
    259                 }
    260             }
    261         } else if (hashes.length == BASE_SIZE) {
    262             synchronized (ArrayMap.class) {
    263                 if (mBaseCacheSize < CACHE_SIZE) {
    264                     array[0] = mBaseCache;
    265                     array[1] = hashes;
    266                     for (int i=(size<<1)-1; i>=2; i--) {
    267                         array[i] = null;
    268                     }
    269                     mBaseCache = array;
    270                     mBaseCacheSize++;
    271                     if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
    272                             + " now have " + mBaseCacheSize + " entries");
    273                 }
    274             }
    275         }
    276     }
    277 
    278     /**
    279      * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
    280      * will grow once items are added to it.
    281      */
    282     public ArrayMap() {
    283         this(0, false);
    284     }
    285 
    286     /**
    287      * Create a new ArrayMap with a given initial capacity.
    288      */
    289     public ArrayMap(int capacity) {
    290         this(capacity, false);
    291     }
    292 
    293     /** {@hide} */
    294     public ArrayMap(int capacity, boolean identityHashCode) {
    295         mIdentityHashCode = identityHashCode;
    296 
    297         // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
    298         // instance instead of the usual EmptyArray.INT. The reference
    299         // is checked later to see if the array is allowed to grow.
    300         if (capacity < 0) {
    301             mHashes = EMPTY_IMMUTABLE_INTS;
    302             mArray = EmptyArray.OBJECT;
    303         } else if (capacity == 0) {
    304             mHashes = EmptyArray.INT;
    305             mArray = EmptyArray.OBJECT;
    306         } else {
    307             allocArrays(capacity);
    308         }
    309         mSize = 0;
    310     }
    311 
    312     /**
    313      * Create a new ArrayMap with the mappings from the given ArrayMap.
    314      */
    315     public ArrayMap(ArrayMap<K, V> map) {
    316         this();
    317         if (map != null) {
    318             putAll(map);
    319         }
    320     }
    321 
    322     /**
    323      * Make the array map empty.  All storage is released.
    324      */
    325     @Override
    326     public void clear() {
    327         if (mSize > 0) {
    328             final int[] ohashes = mHashes;
    329             final Object[] oarray = mArray;
    330             final int osize = mSize;
    331             mHashes = EmptyArray.INT;
    332             mArray = EmptyArray.OBJECT;
    333             mSize = 0;
    334             freeArrays(ohashes, oarray, osize);
    335         }
    336         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
    337             throw new ConcurrentModificationException();
    338         }
    339     }
    340 
    341     /**
    342      * @hide
    343      * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
    344      */
    345     public void erase() {
    346         if (mSize > 0) {
    347             final int N = mSize<<1;
    348             final Object[] array = mArray;
    349             for (int i=0; i<N; i++) {
    350                 array[i] = null;
    351             }
    352             mSize = 0;
    353         }
    354     }
    355 
    356     /**
    357      * Ensure the array map can hold at least <var>minimumCapacity</var>
    358      * items.
    359      */
    360     public void ensureCapacity(int minimumCapacity) {
    361         final int osize = mSize;
    362         if (mHashes.length < minimumCapacity) {
    363             final int[] ohashes = mHashes;
    364             final Object[] oarray = mArray;
    365             allocArrays(minimumCapacity);
    366             if (mSize > 0) {
    367                 System.arraycopy(ohashes, 0, mHashes, 0, osize);
    368                 System.arraycopy(oarray, 0, mArray, 0, osize<<1);
    369             }
    370             freeArrays(ohashes, oarray, osize);
    371         }
    372         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
    373             throw new ConcurrentModificationException();
    374         }
    375     }
    376 
    377     /**
    378      * Check whether a key exists in the array.
    379      *
    380      * @param key The key to search for.
    381      * @return Returns true if the key exists, else false.
    382      */
    383     @Override
    384     public boolean containsKey(Object key) {
    385         return indexOfKey(key) >= 0;
    386     }
    387 
    388     /**
    389      * Returns the index of a key in the set.
    390      *
    391      * @param key The key to search for.
    392      * @return Returns the index of the key if it exists, else a negative integer.
    393      */
    394     public int indexOfKey(Object key) {
    395         return key == null ? indexOfNull()
    396                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
    397     }
    398 
    399     /**
    400      * Returns an index for which {@link #valueAt} would return the
    401      * specified value, or a negative number if no keys map to the
    402      * specified value.
    403      * Beware that this is a linear search, unlike lookups by key,
    404      * and that multiple keys can map to the same value and this will
    405      * find only one of them.
    406      */
    407     public int indexOfValue(Object value) {
    408         final int N = mSize*2;
    409         final Object[] array = mArray;
    410         if (value == null) {
    411             for (int i=1; i<N; i+=2) {
    412                 if (array[i] == null) {
    413                     return i>>1;
    414                 }
    415             }
    416         } else {
    417             for (int i=1; i<N; i+=2) {
    418                 if (value.equals(array[i])) {
    419                     return i>>1;
    420                 }
    421             }
    422         }
    423         return -1;
    424     }
    425 
    426     /**
    427      * Check whether a value exists in the array.  This requires a linear search
    428      * through the entire array.
    429      *
    430      * @param value The value to search for.
    431      * @return Returns true if the value exists, else false.
    432      */
    433     @Override
    434     public boolean containsValue(Object value) {
    435         return indexOfValue(value) >= 0;
    436     }
    437 
    438     /**
    439      * Retrieve a value from the array.
    440      * @param key The key of the value to retrieve.
    441      * @return Returns the value associated with the given key,
    442      * or null if there is no such key.
    443      */
    444     @Override
    445     public V get(Object key) {
    446         final int index = indexOfKey(key);
    447         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
    448     }
    449 
    450     /**
    451      * Return the key at the given index in the array.
    452      *
    453      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    454      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    455      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    456      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    457      *
    458      * @param index The desired index, must be between 0 and {@link #size()}-1.
    459      * @return Returns the key stored at the given index.
    460      */
    461     public K keyAt(int index) {
    462         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    463             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    464             // Check if exception should be thrown outside of the critical path.
    465             throw new ArrayIndexOutOfBoundsException(index);
    466         }
    467         return (K)mArray[index << 1];
    468     }
    469 
    470     /**
    471      * Return the value at the given index in the array.
    472      *
    473      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    474      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    475      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    476      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    477      *
    478      * @param index The desired index, must be between 0 and {@link #size()}-1.
    479      * @return Returns the value stored at the given index.
    480      */
    481     public V valueAt(int index) {
    482         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    483             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    484             // Check if exception should be thrown outside of the critical path.
    485             throw new ArrayIndexOutOfBoundsException(index);
    486         }
    487         return (V)mArray[(index << 1) + 1];
    488     }
    489 
    490     /**
    491      * Set the value at a given index in the array.
    492      *
    493      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    494      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    495      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    496      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    497      *
    498      * @param index The desired index, must be between 0 and {@link #size()}-1.
    499      * @param value The new value to store at this index.
    500      * @return Returns the previous value at the given index.
    501      */
    502     public V setValueAt(int index, V value) {
    503         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    504             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    505             // Check if exception should be thrown outside of the critical path.
    506             throw new ArrayIndexOutOfBoundsException(index);
    507         }
    508         index = (index << 1) + 1;
    509         V old = (V)mArray[index];
    510         mArray[index] = value;
    511         return old;
    512     }
    513 
    514     /**
    515      * Return true if the array map contains no items.
    516      */
    517     @Override
    518     public boolean isEmpty() {
    519         return mSize <= 0;
    520     }
    521 
    522     /**
    523      * Add a new value to the array map.
    524      * @param key The key under which to store the value.  If
    525      * this key already exists in the array, its value will be replaced.
    526      * @param value The value to store for the given key.
    527      * @return Returns the old value that was stored for the given key, or null if there
    528      * was no such key.
    529      */
    530     @Override
    531     public V put(K key, V value) {
    532         final int osize = mSize;
    533         final int hash;
    534         int index;
    535         if (key == null) {
    536             hash = 0;
    537             index = indexOfNull();
    538         } else {
    539             hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
    540             index = indexOf(key, hash);
    541         }
    542         if (index >= 0) {
    543             index = (index<<1) + 1;
    544             final V old = (V)mArray[index];
    545             mArray[index] = value;
    546             return old;
    547         }
    548 
    549         index = ~index;
    550         if (osize >= mHashes.length) {
    551             final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
    552                     : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
    553 
    554             if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
    555 
    556             final int[] ohashes = mHashes;
    557             final Object[] oarray = mArray;
    558             allocArrays(n);
    559 
    560             if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
    561                 throw new ConcurrentModificationException();
    562             }
    563 
    564             if (mHashes.length > 0) {
    565                 if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
    566                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
    567                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
    568             }
    569 
    570             freeArrays(ohashes, oarray, osize);
    571         }
    572 
    573         if (index < osize) {
    574             if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
    575                     + " to " + (index+1));
    576             System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
    577             System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    578         }
    579 
    580         if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
    581             if (osize != mSize || index >= mHashes.length) {
    582                 throw new ConcurrentModificationException();
    583             }
    584         }
    585         mHashes[index] = hash;
    586         mArray[index<<1] = key;
    587         mArray[(index<<1)+1] = value;
    588         mSize++;
    589         return null;
    590     }
    591 
    592     /**
    593      * Special fast path for appending items to the end of the array without validation.
    594      * The array must already be large enough to contain the item.
    595      * @hide
    596      */
    597     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
    598     public void append(K key, V value) {
    599         int index = mSize;
    600         final int hash = key == null ? 0
    601                 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
    602         if (index >= mHashes.length) {
    603             throw new IllegalStateException("Array is full");
    604         }
    605         if (index > 0 && mHashes[index-1] > hash) {
    606             RuntimeException e = new RuntimeException("here");
    607             e.fillInStackTrace();
    608             Log.w(TAG, "New hash " + hash
    609                     + " is before end of array hash " + mHashes[index-1]
    610                     + " at index " + index + " key " + key, e);
    611             put(key, value);
    612             return;
    613         }
    614         mSize = index+1;
    615         mHashes[index] = hash;
    616         index <<= 1;
    617         mArray[index] = key;
    618         mArray[index+1] = value;
    619     }
    620 
    621     /**
    622      * The use of the {@link #append} function can result in invalid array maps, in particular
    623      * an array map where the same key appears multiple times.  This function verifies that
    624      * the array map is valid, throwing IllegalArgumentException if a problem is found.  The
    625      * main use for this method is validating an array map after unpacking from an IPC, to
    626      * protect against malicious callers.
    627      * @hide
    628      */
    629     public void validate() {
    630         final int N = mSize;
    631         if (N <= 1) {
    632             // There can't be dups.
    633             return;
    634         }
    635         int basehash = mHashes[0];
    636         int basei = 0;
    637         for (int i=1; i<N; i++) {
    638             int hash = mHashes[i];
    639             if (hash != basehash) {
    640                 basehash = hash;
    641                 basei = i;
    642                 continue;
    643             }
    644             // We are in a run of entries with the same hash code.  Go backwards through
    645             // the array to see if any keys are the same.
    646             final Object cur = mArray[i<<1];
    647             for (int j=i-1; j>=basei; j--) {
    648                 final Object prev = mArray[j<<1];
    649                 if (cur == prev) {
    650                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
    651                 }
    652                 if (cur != null && prev != null && cur.equals(prev)) {
    653                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
    654                 }
    655             }
    656         }
    657     }
    658 
    659     /**
    660      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
    661      * @param array The array whose contents are to be retrieved.
    662      */
    663     public void putAll(ArrayMap<? extends K, ? extends V> array) {
    664         final int N = array.mSize;
    665         ensureCapacity(mSize + N);
    666         if (mSize == 0) {
    667             if (N > 0) {
    668                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
    669                 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
    670                 mSize = N;
    671             }
    672         } else {
    673             for (int i=0; i<N; i++) {
    674                 put(array.keyAt(i), array.valueAt(i));
    675             }
    676         }
    677     }
    678 
    679     /**
    680      * Remove an existing key from the array map.
    681      * @param key The key of the mapping to remove.
    682      * @return Returns the value that was stored under the key, or null if there
    683      * was no such key.
    684      */
    685     @Override
    686     public V remove(Object key) {
    687         final int index = indexOfKey(key);
    688         if (index >= 0) {
    689             return removeAt(index);
    690         }
    691 
    692         return null;
    693     }
    694 
    695     /**
    696      * Remove the key/value mapping at the given index.
    697      *
    698      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    699      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    700      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    701      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    702      *
    703      * @param index The desired index, must be between 0 and {@link #size()}-1.
    704      * @return Returns the value that was stored at this index.
    705      */
    706     public V removeAt(int index) {
    707         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    708             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    709             // Check if exception should be thrown outside of the critical path.
    710             throw new ArrayIndexOutOfBoundsException(index);
    711         }
    712 
    713         final Object old = mArray[(index << 1) + 1];
    714         final int osize = mSize;
    715         final int nsize;
    716         if (osize <= 1) {
    717             // Now empty.
    718             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
    719             final int[] ohashes = mHashes;
    720             final Object[] oarray = mArray;
    721             mHashes = EmptyArray.INT;
    722             mArray = EmptyArray.OBJECT;
    723             freeArrays(ohashes, oarray, osize);
    724             nsize = 0;
    725         } else {
    726             nsize = osize - 1;
    727             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
    728                 // Shrunk enough to reduce size of arrays.  We don't allow it to
    729                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
    730                 // that and BASE_SIZE.
    731                 final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
    732 
    733                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
    734 
    735                 final int[] ohashes = mHashes;
    736                 final Object[] oarray = mArray;
    737                 allocArrays(n);
    738 
    739                 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
    740                     throw new ConcurrentModificationException();
    741                 }
    742 
    743                 if (index > 0) {
    744                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
    745                     System.arraycopy(ohashes, 0, mHashes, 0, index);
    746                     System.arraycopy(oarray, 0, mArray, 0, index << 1);
    747                 }
    748                 if (index < nsize) {
    749                     if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
    750                             + " to " + index);
    751                     System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
    752                     System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
    753                             (nsize - index) << 1);
    754                 }
    755             } else {
    756                 if (index < nsize) {
    757                     if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
    758                             + " to " + index);
    759                     System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
    760                     System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
    761                             (nsize - index) << 1);
    762                 }
    763                 mArray[nsize << 1] = null;
    764                 mArray[(nsize << 1) + 1] = null;
    765             }
    766         }
    767         if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
    768             throw new ConcurrentModificationException();
    769         }
    770         mSize = nsize;
    771         return (V)old;
    772     }
    773 
    774     /**
    775      * Return the number of items in this array map.
    776      */
    777     @Override
    778     public int size() {
    779         return mSize;
    780     }
    781 
    782     /**
    783      * {@inheritDoc}
    784      *
    785      * <p>This implementation returns false if the object is not a map, or
    786      * if the maps have different sizes. Otherwise, for each key in this map,
    787      * values of both maps are compared. If the values for any key are not
    788      * equal, the method returns false, otherwise it returns true.
    789      */
    790     @Override
    791     public boolean equals(Object object) {
    792         if (this == object) {
    793             return true;
    794         }
    795         if (object instanceof Map) {
    796             Map<?, ?> map = (Map<?, ?>) object;
    797             if (size() != map.size()) {
    798                 return false;
    799             }
    800 
    801             try {
    802                 for (int i=0; i<mSize; i++) {
    803                     K key = keyAt(i);
    804                     V mine = valueAt(i);
    805                     Object theirs = map.get(key);
    806                     if (mine == null) {
    807                         if (theirs != null || !map.containsKey(key)) {
    808                             return false;
    809                         }
    810                     } else if (!mine.equals(theirs)) {
    811                         return false;
    812                     }
    813                 }
    814             } catch (NullPointerException ignored) {
    815                 return false;
    816             } catch (ClassCastException ignored) {
    817                 return false;
    818             }
    819             return true;
    820         }
    821         return false;
    822     }
    823 
    824     /**
    825      * {@inheritDoc}
    826      */
    827     @Override
    828     public int hashCode() {
    829         final int[] hashes = mHashes;
    830         final Object[] array = mArray;
    831         int result = 0;
    832         for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
    833             Object value = array[v];
    834             result += hashes[i] ^ (value == null ? 0 : value.hashCode());
    835         }
    836         return result;
    837     }
    838 
    839     /**
    840      * {@inheritDoc}
    841      *
    842      * <p>This implementation composes a string by iterating over its mappings. If
    843      * this map contains itself as a key or a value, the string "(this Map)"
    844      * will appear in its place.
    845      */
    846     @Override
    847     public String toString() {
    848         if (isEmpty()) {
    849             return "{}";
    850         }
    851 
    852         StringBuilder buffer = new StringBuilder(mSize * 28);
    853         buffer.append('{');
    854         for (int i=0; i<mSize; i++) {
    855             if (i > 0) {
    856                 buffer.append(", ");
    857             }
    858             Object key = keyAt(i);
    859             if (key != this) {
    860                 buffer.append(key);
    861             } else {
    862                 buffer.append("(this Map)");
    863             }
    864             buffer.append('=');
    865             Object value = valueAt(i);
    866             if (value != this) {
    867                 buffer.append(ArrayUtils.deepToString(value));
    868             } else {
    869                 buffer.append("(this Map)");
    870             }
    871         }
    872         buffer.append('}');
    873         return buffer.toString();
    874     }
    875 
    876     // ------------------------------------------------------------------------
    877     // Interop with traditional Java containers.  Not as efficient as using
    878     // specialized collection APIs.
    879     // ------------------------------------------------------------------------
    880 
    881     private MapCollections<K, V> getCollection() {
    882         if (mCollections == null) {
    883             mCollections = new MapCollections<K, V>() {
    884                 @Override
    885                 protected int colGetSize() {
    886                     return mSize;
    887                 }
    888 
    889                 @Override
    890                 protected Object colGetEntry(int index, int offset) {
    891                     return mArray[(index<<1) + offset];
    892                 }
    893 
    894                 @Override
    895                 protected int colIndexOfKey(Object key) {
    896                     return indexOfKey(key);
    897                 }
    898 
    899                 @Override
    900                 protected int colIndexOfValue(Object value) {
    901                     return indexOfValue(value);
    902                 }
    903 
    904                 @Override
    905                 protected Map<K, V> colGetMap() {
    906                     return ArrayMap.this;
    907                 }
    908 
    909                 @Override
    910                 protected void colPut(K key, V value) {
    911                     put(key, value);
    912                 }
    913 
    914                 @Override
    915                 protected V colSetValue(int index, V value) {
    916                     return setValueAt(index, value);
    917                 }
    918 
    919                 @Override
    920                 protected void colRemoveAt(int index) {
    921                     removeAt(index);
    922                 }
    923 
    924                 @Override
    925                 protected void colClear() {
    926                     clear();
    927                 }
    928             };
    929         }
    930         return mCollections;
    931     }
    932 
    933     /**
    934      * Determine if the array map contains all of the keys in the given collection.
    935      * @param collection The collection whose contents are to be checked against.
    936      * @return Returns true if this array map contains a key for every entry
    937      * in <var>collection</var>, else returns false.
    938      */
    939     public boolean containsAll(Collection<?> collection) {
    940         return MapCollections.containsAllHelper(this, collection);
    941     }
    942 
    943     /**
    944      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
    945      * @param map The map whose contents are to be retrieved.
    946      */
    947     @Override
    948     public void putAll(Map<? extends K, ? extends V> map) {
    949         ensureCapacity(mSize + map.size());
    950         for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
    951             put(entry.getKey(), entry.getValue());
    952         }
    953     }
    954 
    955     /**
    956      * Remove all keys in the array map that exist in the given collection.
    957      * @param collection The collection whose contents are to be used to remove keys.
    958      * @return Returns true if any keys were removed from the array map, else false.
    959      */
    960     public boolean removeAll(Collection<?> collection) {
    961         return MapCollections.removeAllHelper(this, collection);
    962     }
    963 
    964     /**
    965      * Remove all keys in the array map that do <b>not</b> exist in the given collection.
    966      * @param collection The collection whose contents are to be used to determine which
    967      * keys to keep.
    968      * @return Returns true if any keys were removed from the array map, else false.
    969      */
    970     public boolean retainAll(Collection<?> collection) {
    971         return MapCollections.retainAllHelper(this, collection);
    972     }
    973 
    974     /**
    975      * Return a {@link java.util.Set} for iterating over and interacting with all mappings
    976      * in the array map.
    977      *
    978      * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
    979      * requires generating a number of temporary objects and allocates additional state
    980      * information associated with the container that will remain for the life of the container.</p>
    981      *
    982      * <p><b>Note:</b></p> the semantics of this
    983      * Set are subtly different than that of a {@link java.util.HashMap}: most important,
    984      * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
    985      * object that exists for the entire iterator, so you can <b>not</b> hold on to it
    986      * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
    987      */
    988     @Override
    989     public Set<Map.Entry<K, V>> entrySet() {
    990         return getCollection().getEntrySet();
    991     }
    992 
    993     /**
    994      * Return a {@link java.util.Set} for iterating over and interacting with all keys
    995      * in the array map.
    996      *
    997      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
    998      * requires generating a number of temporary objects and allocates additional state
    999      * information associated with the container that will remain for the life of the container.</p>
   1000      */
   1001     @Override
   1002     public Set<K> keySet() {
   1003         return getCollection().getKeySet();
   1004     }
   1005 
   1006     /**
   1007      * Return a {@link java.util.Collection} for iterating over and interacting with all values
   1008      * in the array map.
   1009      *
   1010      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
   1011      * requires generating a number of temporary objects and allocates additional state
   1012      * information associated with the container that will remain for the life of the container.</p>
   1013      */
   1014     @Override
   1015     public Collection<V> values() {
   1016         return getCollection().getValues();
   1017     }
   1018 }
   1019