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.TestApi;
     20 import android.annotation.UnsupportedAppUsage;
     21 
     22 import libcore.util.EmptyArray;
     23 
     24 import java.lang.reflect.Array;
     25 import java.util.Collection;
     26 import java.util.Iterator;
     27 import java.util.Map;
     28 import java.util.Set;
     29 import java.util.function.Predicate;
     30 
     31 /**
     32  * ArraySet is a generic set data structure that is designed to be more memory efficient than a
     33  * traditional {@link java.util.HashSet}.  The design is very similar to
     34  * {@link ArrayMap}, with all of the caveats described there.  This implementation is
     35  * separate from ArrayMap, however, so the Object array contains only one item for each
     36  * entry in the set (instead of a pair for a mapping).
     37  *
     38  * <p>Note that this implementation is not intended to be appropriate for data structures
     39  * that may contain large numbers of items.  It is generally slower than a traditional
     40  * HashSet, since lookups require a binary search and adds and removes require inserting
     41  * and deleting entries in the array.  For containers holding up to hundreds of items,
     42  * the performance difference is not significant, less than 50%.</p>
     43  *
     44  * <p>Because this container is intended to better balance memory use, unlike most other
     45  * standard Java containers it will shrink its array as items are removed from it.  Currently
     46  * you have no control over this shrinking -- if you set a capacity and then remove an
     47  * item, it may reduce the capacity to better match the current size.  In the future an
     48  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
     49  */
     50 public final class ArraySet<E> implements Collection<E>, Set<E> {
     51     private static final boolean DEBUG = false;
     52     private static final String TAG = "ArraySet";
     53 
     54     /**
     55      * The minimum amount by which the capacity of a ArraySet will increase.
     56      * This is tuned to be relatively space-efficient.
     57      */
     58     private static final int BASE_SIZE = 4;
     59 
     60     /**
     61      * Maximum number of entries to have in array caches.
     62      */
     63     private static final int CACHE_SIZE = 10;
     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[] sBaseCache;
     72     static int sBaseCacheSize;
     73     static Object[] sTwiceBaseCache;
     74     static int sTwiceBaseCacheSize;
     75 
     76     final boolean mIdentityHashCode;
     77     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
     78     int[] mHashes;
     79     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
     80     Object[] mArray;
     81     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
     82     int mSize;
     83     MapCollections<E, E> mCollections;
     84 
     85     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
     86     private 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])) {
    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])) 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])) 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     @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
    125     private int indexOfNull() {
    126         final int N = mSize;
    127 
    128         // Important fast case: if nothing is in here, nothing to look for.
    129         if (N == 0) {
    130             return ~0;
    131         }
    132 
    133         int index = ContainerHelpers.binarySearch(mHashes, N, 0);
    134 
    135         // If the hash code wasn't found, then we have no entry for this key.
    136         if (index < 0) {
    137             return index;
    138         }
    139 
    140         // If the key at the returned index matches, that's what we want.
    141         if (null == mArray[index]) {
    142             return index;
    143         }
    144 
    145         // Search for a matching key after the index.
    146         int end;
    147         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
    148             if (null == mArray[end]) return end;
    149         }
    150 
    151         // Search for a matching key before the index.
    152         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
    153             if (null == mArray[i]) return i;
    154         }
    155 
    156         // Key not found -- return negative value indicating where a
    157         // new entry for this key should go.  We use the end of the
    158         // hash chain to reduce the number of array entries that will
    159         // need to be copied when inserting.
    160         return ~end;
    161     }
    162 
    163     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    164     private void allocArrays(final int size) {
    165         if (size == (BASE_SIZE * 2)) {
    166             synchronized (ArraySet.class) {
    167                 if (sTwiceBaseCache != null) {
    168                     final Object[] array = sTwiceBaseCache;
    169                     try {
    170                         mArray = array;
    171                         sTwiceBaseCache = (Object[]) array[0];
    172                         mHashes = (int[]) array[1];
    173                         array[0] = array[1] = null;
    174                         sTwiceBaseCacheSize--;
    175                         if (DEBUG) {
    176                             Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
    177                                     + sTwiceBaseCacheSize + " entries");
    178                     }
    179                     return;
    180                     } catch (ClassCastException e) {
    181                     }
    182                     // Whoops!  Someone trampled the array (probably due to not protecting
    183                     // their access with a lock).  Our cache is corrupt; report and give up.
    184                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
    185                             + " [1]=" + array[1]);
    186                     sTwiceBaseCache = null;
    187                     sTwiceBaseCacheSize = 0;
    188                 }
    189             }
    190         } else if (size == BASE_SIZE) {
    191             synchronized (ArraySet.class) {
    192                 if (sBaseCache != null) {
    193                     final Object[] array = sBaseCache;
    194                     try {
    195                         mArray = array;
    196                         sBaseCache = (Object[]) array[0];
    197                         mHashes = (int[]) array[1];
    198                         array[0] = array[1] = null;
    199                         sBaseCacheSize--;
    200                         if (DEBUG) {
    201                             Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
    202                                     + " entries");
    203                         }
    204                         return;
    205                     } catch (ClassCastException e) {
    206                     }
    207                     // Whoops!  Someone trampled the array (probably due to not protecting
    208                     // their access with a lock).  Our cache is corrupt; report and give up.
    209                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
    210                             + " [1]=" + array[1]);
    211                     sBaseCache = null;
    212                     sBaseCacheSize = 0;
    213                 }
    214             }
    215         }
    216 
    217         mHashes = new int[size];
    218         mArray = new Object[size];
    219     }
    220 
    221     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
    222     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    223         if (hashes.length == (BASE_SIZE * 2)) {
    224             synchronized (ArraySet.class) {
    225                 if (sTwiceBaseCacheSize < CACHE_SIZE) {
    226                     array[0] = sTwiceBaseCache;
    227                     array[1] = hashes;
    228                     for (int i = size - 1; i >= 2; i--) {
    229                         array[i] = null;
    230                     }
    231                     sTwiceBaseCache = array;
    232                     sTwiceBaseCacheSize++;
    233                     if (DEBUG) {
    234                         Log.d(TAG, "Storing 2x cache " + array + " now have " + sTwiceBaseCacheSize
    235                                 + " entries");
    236                     }
    237                 }
    238             }
    239         } else if (hashes.length == BASE_SIZE) {
    240             synchronized (ArraySet.class) {
    241                 if (sBaseCacheSize < CACHE_SIZE) {
    242                     array[0] = sBaseCache;
    243                     array[1] = hashes;
    244                     for (int i = size - 1; i >= 2; i--) {
    245                         array[i] = null;
    246                     }
    247                     sBaseCache = array;
    248                     sBaseCacheSize++;
    249                     if (DEBUG) {
    250                         Log.d(TAG, "Storing 1x cache " + array + " now have "
    251                                 + sBaseCacheSize + " entries");
    252                     }
    253                 }
    254             }
    255         }
    256     }
    257 
    258     /**
    259      * Create a new empty ArraySet.  The default capacity of an array map is 0, and
    260      * will grow once items are added to it.
    261      */
    262     public ArraySet() {
    263         this(0, false);
    264     }
    265 
    266     /**
    267      * Create a new ArraySet with a given initial capacity.
    268      */
    269     public ArraySet(int capacity) {
    270         this(capacity, false);
    271     }
    272 
    273     /** {@hide} */
    274     public ArraySet(int capacity, boolean identityHashCode) {
    275         mIdentityHashCode = identityHashCode;
    276         if (capacity == 0) {
    277             mHashes = EmptyArray.INT;
    278             mArray = EmptyArray.OBJECT;
    279         } else {
    280             allocArrays(capacity);
    281         }
    282         mSize = 0;
    283     }
    284 
    285     /**
    286      * Create a new ArraySet with the mappings from the given ArraySet.
    287      */
    288     public ArraySet(ArraySet<E> set) {
    289         this();
    290         if (set != null) {
    291             addAll(set);
    292         }
    293     }
    294 
    295     /**
    296      * Create a new ArraySet with items from the given collection.
    297      */
    298     public ArraySet(Collection<? extends E> set) {
    299         this();
    300         if (set != null) {
    301             addAll(set);
    302         }
    303     }
    304 
    305     /**
    306      * Make the array map empty.  All storage is released.
    307      */
    308     @Override
    309     public void clear() {
    310         if (mSize != 0) {
    311             freeArrays(mHashes, mArray, mSize);
    312             mHashes = EmptyArray.INT;
    313             mArray = EmptyArray.OBJECT;
    314             mSize = 0;
    315         }
    316     }
    317 
    318     /**
    319      * Ensure the array map can hold at least <var>minimumCapacity</var>
    320      * items.
    321      */
    322     public void ensureCapacity(int minimumCapacity) {
    323         if (mHashes.length < minimumCapacity) {
    324             final int[] ohashes = mHashes;
    325             final Object[] oarray = mArray;
    326             allocArrays(minimumCapacity);
    327             if (mSize > 0) {
    328                 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
    329                 System.arraycopy(oarray, 0, mArray, 0, mSize);
    330             }
    331             freeArrays(ohashes, oarray, mSize);
    332         }
    333     }
    334 
    335     /**
    336      * Check whether a value exists in the set.
    337      *
    338      * @param key The value to search for.
    339      * @return Returns true if the value exists, else false.
    340      */
    341     @Override
    342     public boolean contains(Object key) {
    343         return indexOf(key) >= 0;
    344     }
    345 
    346     /**
    347      * Returns the index of a value in the set.
    348      *
    349      * @param key The value to search for.
    350      * @return Returns the index of the value if it exists, else a negative integer.
    351      */
    352     public int indexOf(Object key) {
    353         return key == null ? indexOfNull()
    354                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
    355     }
    356 
    357     /**
    358      * Return the value at the given index in the array.
    359      *
    360      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    361      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    362      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    363      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    364      *
    365      * @param index The desired index, must be between 0 and {@link #size()}-1.
    366      * @return Returns the value stored at the given index.
    367      */
    368     public E valueAt(int index) {
    369         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    370             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    371             // Check if exception should be thrown outside of the critical path.
    372             throw new ArrayIndexOutOfBoundsException(index);
    373         }
    374         return valueAtUnchecked(index);
    375     }
    376 
    377     /**
    378      * Returns the value at the given index in the array without checking that the index is within
    379      * bounds. This allows testing values at the end of the internal array, outside of the
    380      * [0, mSize) bounds.
    381      *
    382      * @hide
    383      */
    384     @TestApi
    385     public E valueAtUnchecked(int index) {
    386         return (E) mArray[index];
    387     }
    388 
    389     /**
    390      * Return true if the array map contains no items.
    391      */
    392     @Override
    393     public boolean isEmpty() {
    394         return mSize <= 0;
    395     }
    396 
    397     /**
    398      * Adds the specified object to this set. The set is not modified if it
    399      * already contains the object.
    400      *
    401      * @param value the object to add.
    402      * @return {@code true} if this set is modified, {@code false} otherwise.
    403      * @throws ClassCastException
    404      *             when the class of the object is inappropriate for this set.
    405      */
    406     @Override
    407     public boolean add(E value) {
    408         final int hash;
    409         int index;
    410         if (value == null) {
    411             hash = 0;
    412             index = indexOfNull();
    413         } else {
    414             hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
    415             index = indexOf(value, hash);
    416         }
    417         if (index >= 0) {
    418             return false;
    419         }
    420 
    421         index = ~index;
    422         if (mSize >= mHashes.length) {
    423             final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
    424                     : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
    425 
    426             if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
    427 
    428             final int[] ohashes = mHashes;
    429             final Object[] oarray = mArray;
    430             allocArrays(n);
    431 
    432             if (mHashes.length > 0) {
    433                 if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
    434                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
    435                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
    436             }
    437 
    438             freeArrays(ohashes, oarray, mSize);
    439         }
    440 
    441         if (index < mSize) {
    442             if (DEBUG) {
    443                 Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
    444             }
    445             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
    446             System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
    447         }
    448 
    449         mHashes[index] = hash;
    450         mArray[index] = value;
    451         mSize++;
    452         return true;
    453     }
    454 
    455     /**
    456      * Special fast path for appending items to the end of the array without validation.
    457      * The array must already be large enough to contain the item.
    458      * @hide
    459      */
    460     public void append(E value) {
    461         final int index = mSize;
    462         final int hash = value == null ? 0
    463                 : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
    464         if (index >= mHashes.length) {
    465             throw new IllegalStateException("Array is full");
    466         }
    467         if (index > 0 && mHashes[index - 1] > hash) {
    468             // Cannot optimize since it would break the sorted order - fallback to add()
    469             if (DEBUG) {
    470                 RuntimeException e = new RuntimeException("here");
    471                 e.fillInStackTrace();
    472                 Log.w(TAG, "New hash " + hash
    473                         + " is before end of array hash " + mHashes[index - 1]
    474                         + " at index " + index, e);
    475             }
    476             add(value);
    477             return;
    478         }
    479         mSize = index + 1;
    480         mHashes[index] = hash;
    481         mArray[index] = value;
    482     }
    483 
    484     /**
    485      * Perform a {@link #add(Object)} of all values in <var>array</var>
    486      * @param array The array whose contents are to be retrieved.
    487      */
    488     public void addAll(ArraySet<? extends E> array) {
    489         final int N = array.mSize;
    490         ensureCapacity(mSize + N);
    491         if (mSize == 0) {
    492             if (N > 0) {
    493                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
    494                 System.arraycopy(array.mArray, 0, mArray, 0, N);
    495                 mSize = N;
    496             }
    497         } else {
    498             for (int i = 0; i < N; i++) {
    499                 add(array.valueAt(i));
    500             }
    501         }
    502     }
    503 
    504     /**
    505      * Removes the specified object from this set.
    506      *
    507      * @param object the object to remove.
    508      * @return {@code true} if this set was modified, {@code false} otherwise.
    509      */
    510     @Override
    511     public boolean remove(Object object) {
    512         final int index = indexOf(object);
    513         if (index >= 0) {
    514             removeAt(index);
    515             return true;
    516         }
    517         return false;
    518     }
    519 
    520     /** Returns true if the array size should be decreased. */
    521     private boolean shouldShrink() {
    522         return mHashes.length > (BASE_SIZE * 2) && mSize < mHashes.length / 3;
    523     }
    524 
    525     /**
    526      * Returns the new size the array should have. Is only valid if {@link #shouldShrink} returns
    527      * true.
    528      */
    529     private int getNewShrunkenSize() {
    530         // We don't allow it to shrink smaller than (BASE_SIZE*2) to avoid flapping between that
    531         // and BASE_SIZE.
    532         return mSize > (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (BASE_SIZE * 2);
    533     }
    534 
    535     /**
    536      * Remove the key/value mapping at the given index.
    537      *
    538      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    539      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    540      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    541      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    542      *
    543      * @param index The desired index, must be between 0 and {@link #size()}-1.
    544      * @return Returns the value that was stored at this index.
    545      */
    546     public E removeAt(int index) {
    547         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    548             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    549             // Check if exception should be thrown outside of the critical path.
    550             throw new ArrayIndexOutOfBoundsException(index);
    551         }
    552         final Object old = mArray[index];
    553         if (mSize <= 1) {
    554             // Now empty.
    555             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
    556             clear();
    557         } else {
    558             if (shouldShrink()) {
    559                 // Shrunk enough to reduce size of arrays.
    560                 final int n = getNewShrunkenSize();
    561 
    562                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
    563 
    564                 final int[] ohashes = mHashes;
    565                 final Object[] oarray = mArray;
    566                 allocArrays(n);
    567 
    568                 mSize--;
    569                 if (index > 0) {
    570                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
    571                     System.arraycopy(ohashes, 0, mHashes, 0, index);
    572                     System.arraycopy(oarray, 0, mArray, 0, index);
    573                 }
    574                 if (index < mSize) {
    575                     if (DEBUG) {
    576                         Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
    577                                 + " to " + index);
    578                     }
    579                     System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
    580                     System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
    581                 }
    582             } else {
    583                 mSize--;
    584                 if (index < mSize) {
    585                     if (DEBUG) {
    586                         Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
    587                     }
    588                     System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
    589                     System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
    590                 }
    591                 mArray[mSize] = null;
    592             }
    593         }
    594         return (E) old;
    595     }
    596 
    597     /**
    598      * Perform a {@link #remove(Object)} of all values in <var>array</var>
    599      * @param array The array whose contents are to be removed.
    600      */
    601     public boolean removeAll(ArraySet<? extends E> array) {
    602         // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
    603         //       pass, use the property that the sets are sorted by hash to make this linear passes
    604         //       (except for hash collisions, which means worst case still n*m), then do one
    605         //       collection pass into a new array. This avoids binary searches and excessive memcpy.
    606         final int N = array.mSize;
    607 
    608         // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
    609         //       the single results, compare size before and after.
    610         final int originalSize = mSize;
    611         for (int i = 0; i < N; i++) {
    612             remove(array.valueAt(i));
    613         }
    614         return originalSize != mSize;
    615     }
    616 
    617     /**
    618      * Removes all values that satisfy the predicate. This implementation avoids using the
    619      * {@link #iterator()}.
    620      *
    621      * @param filter A predicate which returns true for elements to be removed
    622      */
    623     @Override
    624     public boolean removeIf(Predicate<? super E> filter) {
    625         if (mSize == 0) {
    626             return false;
    627         }
    628 
    629         // Intentionally not using removeAt() to avoid unnecessary intermediate resizing.
    630 
    631         int replaceIndex = 0;
    632         int numRemoved = 0;
    633         for (int i = 0; i < mSize; ++i) {
    634             if (filter.test((E) mArray[i])) {
    635                 numRemoved++;
    636             } else {
    637                 if (replaceIndex != i) {
    638                     mArray[replaceIndex] = mArray[i];
    639                     mHashes[replaceIndex] = mHashes[i];
    640                 }
    641                 replaceIndex++;
    642             }
    643         }
    644 
    645         if (numRemoved == 0) {
    646             return false;
    647         } else if (numRemoved == mSize) {
    648             clear();
    649             return true;
    650         }
    651 
    652         mSize -= numRemoved;
    653         if (shouldShrink()) {
    654             // Shrunk enough to reduce size of arrays.
    655             final int n = getNewShrunkenSize();
    656             final int[] ohashes = mHashes;
    657             final Object[] oarray = mArray;
    658             allocArrays(n);
    659 
    660             System.arraycopy(ohashes, 0, mHashes, 0, mSize);
    661             System.arraycopy(oarray, 0, mArray, 0, mSize);
    662         } else {
    663             // Null out values at the end of the array. Not doing it in the loop above to avoid
    664             // writing twice to the same index or writing unnecessarily if the array would have been
    665             // discarded anyway.
    666             for (int i = mSize; i < mArray.length; ++i) {
    667                 mArray[i] = null;
    668             }
    669         }
    670         return true;
    671     }
    672 
    673     /**
    674      * Return the number of items in this array map.
    675      */
    676     @Override
    677     public int size() {
    678         return mSize;
    679     }
    680 
    681     @Override
    682     public Object[] toArray() {
    683         Object[] result = new Object[mSize];
    684         System.arraycopy(mArray, 0, result, 0, mSize);
    685         return result;
    686     }
    687 
    688     @Override
    689     public <T> T[] toArray(T[] array) {
    690         if (array.length < mSize) {
    691             @SuppressWarnings("unchecked") T[] newArray =
    692                     (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
    693             array = newArray;
    694         }
    695         System.arraycopy(mArray, 0, array, 0, mSize);
    696         if (array.length > mSize) {
    697             array[mSize] = null;
    698         }
    699         return array;
    700     }
    701 
    702     /**
    703      * {@inheritDoc}
    704      *
    705      * <p>This implementation returns false if the object is not a set, or
    706      * if the sets have different sizes.  Otherwise, for each value in this
    707      * set, it checks to make sure the value also exists in the other set.
    708      * If any value doesn't exist, the method returns false; otherwise, it
    709      * returns true.
    710      */
    711     @Override
    712     public boolean equals(Object object) {
    713         if (this == object) {
    714             return true;
    715         }
    716         if (object instanceof Set) {
    717             Set<?> set = (Set<?>) object;
    718             if (size() != set.size()) {
    719                 return false;
    720             }
    721 
    722             try {
    723                 for (int i = 0; i < mSize; i++) {
    724                     E mine = valueAt(i);
    725                     if (!set.contains(mine)) {
    726                         return false;
    727                     }
    728                 }
    729             } catch (NullPointerException ignored) {
    730                 return false;
    731             } catch (ClassCastException ignored) {
    732                 return false;
    733             }
    734             return true;
    735         }
    736         return false;
    737     }
    738 
    739     /**
    740      * {@inheritDoc}
    741      */
    742     @Override
    743     public int hashCode() {
    744         final int[] hashes = mHashes;
    745         int result = 0;
    746         for (int i = 0, s = mSize; i < s; i++) {
    747             result += hashes[i];
    748         }
    749         return result;
    750     }
    751 
    752     /**
    753      * {@inheritDoc}
    754      *
    755      * <p>This implementation composes a string by iterating over its values. If
    756      * this set contains itself as a value, the string "(this Set)"
    757      * will appear in its place.
    758      */
    759     @Override
    760     public String toString() {
    761         if (isEmpty()) {
    762             return "{}";
    763         }
    764 
    765         StringBuilder buffer = new StringBuilder(mSize * 14);
    766         buffer.append('{');
    767         for (int i = 0; i < mSize; i++) {
    768             if (i > 0) {
    769                 buffer.append(", ");
    770             }
    771             Object value = valueAt(i);
    772             if (value != this) {
    773                 buffer.append(value);
    774             } else {
    775                 buffer.append("(this Set)");
    776             }
    777         }
    778         buffer.append('}');
    779         return buffer.toString();
    780     }
    781 
    782     // ------------------------------------------------------------------------
    783     // Interop with traditional Java containers.  Not as efficient as using
    784     // specialized collection APIs.
    785     // ------------------------------------------------------------------------
    786 
    787     private MapCollections<E, E> getCollection() {
    788         if (mCollections == null) {
    789             mCollections = new MapCollections<E, E>() {
    790                 @Override
    791                 protected int colGetSize() {
    792                     return mSize;
    793                 }
    794 
    795                 @Override
    796                 protected Object colGetEntry(int index, int offset) {
    797                     return mArray[index];
    798                 }
    799 
    800                 @Override
    801                 protected int colIndexOfKey(Object key) {
    802                     return indexOf(key);
    803                 }
    804 
    805                 @Override
    806                 protected int colIndexOfValue(Object value) {
    807                     return indexOf(value);
    808                 }
    809 
    810                 @Override
    811                 protected Map<E, E> colGetMap() {
    812                     throw new UnsupportedOperationException("not a map");
    813                 }
    814 
    815                 @Override
    816                 protected void colPut(E key, E value) {
    817                     add(key);
    818                 }
    819 
    820                 @Override
    821                 protected E colSetValue(int index, E value) {
    822                     throw new UnsupportedOperationException("not a map");
    823                 }
    824 
    825                 @Override
    826                 protected void colRemoveAt(int index) {
    827                     removeAt(index);
    828                 }
    829 
    830                 @Override
    831                 protected void colClear() {
    832                     clear();
    833                 }
    834             };
    835         }
    836         return mCollections;
    837     }
    838 
    839     /**
    840      * Return an {@link java.util.Iterator} over all values in the set.
    841      *
    842      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
    843      * requires generating a number of temporary objects and allocates additional state
    844      * information associated with the container that will remain for the life of the container.</p>
    845      */
    846     @Override
    847     public Iterator<E> iterator() {
    848         return getCollection().getKeySet().iterator();
    849     }
    850 
    851     /**
    852      * Determine if the array set contains all of the values in the given collection.
    853      * @param collection The collection whose contents are to be checked against.
    854      * @return Returns true if this array set contains a value for every entry
    855      * in <var>collection</var>, else returns false.
    856      */
    857     @Override
    858     public boolean containsAll(Collection<?> collection) {
    859         Iterator<?> it = collection.iterator();
    860         while (it.hasNext()) {
    861             if (!contains(it.next())) {
    862                 return false;
    863             }
    864         }
    865         return true;
    866     }
    867 
    868     /**
    869      * Perform an {@link #add(Object)} of all values in <var>collection</var>
    870      * @param collection The collection whose contents are to be retrieved.
    871      */
    872     @Override
    873     public boolean addAll(Collection<? extends E> collection) {
    874         ensureCapacity(mSize + collection.size());
    875         boolean added = false;
    876         for (E value : collection) {
    877             added |= add(value);
    878         }
    879         return added;
    880     }
    881 
    882     /**
    883      * Remove all values in the array set that exist in the given collection.
    884      * @param collection The collection whose contents are to be used to remove values.
    885      * @return Returns true if any values were removed from the array set, else false.
    886      */
    887     @Override
    888     public boolean removeAll(Collection<?> collection) {
    889         boolean removed = false;
    890         for (Object value : collection) {
    891             removed |= remove(value);
    892         }
    893         return removed;
    894     }
    895 
    896     /**
    897      * Remove all values in the array set that do <b>not</b> exist in the given collection.
    898      * @param collection The collection whose contents are to be used to determine which
    899      * values to keep.
    900      * @return Returns true if any values were removed from the array set, else false.
    901      */
    902     @Override
    903     public boolean retainAll(Collection<?> collection) {
    904         boolean removed = false;
    905         for (int i = mSize - 1; i >= 0; i--) {
    906             if (!collection.contains(mArray[i])) {
    907                 removeAt(i);
    908                 removed = true;
    909             }
    910         }
    911         return removed;
    912     }
    913 }
    914