• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import libcore.util.EmptyArray;
20 
21 import java.lang.reflect.Array;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26 
27 /**
28  * ArraySet is a generic set data structure that is designed to be more memory efficient than a
29  * traditional {@link java.util.HashSet}.  The design is very similar to
30  * {@link ArrayMap}, with all of the caveats described there.  This implementation is
31  * separate from ArrayMap, however, so the Object array contains only one item for each
32  * entry in the set (instead of a pair for a mapping).
33  *
34  * <p>Note that this implementation is not intended to be appropriate for data structures
35  * that may contain large numbers of items.  It is generally slower than a traditional
36  * HashSet, since lookups require a binary search and adds and removes require inserting
37  * and deleting entries in the array.  For containers holding up to hundreds of items,
38  * the performance difference is not significant, less than 50%.</p>
39  *
40  * <p>Because this container is intended to better balance memory use, unlike most other
41  * standard Java containers it will shrink its array as items are removed from it.  Currently
42  * you have no control over this shrinking -- if you set a capacity and then remove an
43  * item, it may reduce the capacity to better match the current size.  In the future an
44  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
45  *
46  * @hide
47  */
48 public final class ArraySet<E> implements Collection<E>, Set<E> {
49     private static final boolean DEBUG = false;
50     private static final String TAG = "ArraySet";
51 
52     /**
53      * The minimum amount by which the capacity of a ArraySet will increase.
54      * This is tuned to be relatively space-efficient.
55      */
56     private static final int BASE_SIZE = 4;
57 
58     /**
59      * Maximum number of entries to have in array caches.
60      */
61     private static final int CACHE_SIZE = 10;
62 
63     /**
64      * Caches of small array objects to avoid spamming garbage.  The cache
65      * Object[] variable is a pointer to a linked list of array objects.
66      * The first entry in the array is a pointer to the next array in the
67      * list; the second entry is a pointer to the int[] hash code array for it.
68      */
69     static Object[] mBaseCache;
70     static int mBaseCacheSize;
71     static Object[] mTwiceBaseCache;
72     static int mTwiceBaseCacheSize;
73 
74     int[] mHashes;
75     Object[] mArray;
76     int mSize;
77     MapCollections<E, E> mCollections;
78 
indexOf(Object key, int hash)79     private int indexOf(Object key, int hash) {
80         final int N = mSize;
81 
82         // Important fast case: if nothing is in here, nothing to look for.
83         if (N == 0) {
84             return ~0;
85         }
86 
87         int index = ContainerHelpers.binarySearch(mHashes, N, hash);
88 
89         // If the hash code wasn't found, then we have no entry for this key.
90         if (index < 0) {
91             return index;
92         }
93 
94         // If the key at the returned index matches, that's what we want.
95         if (key.equals(mArray[index])) {
96             return index;
97         }
98 
99         // Search for a matching key after the index.
100         int end;
101         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
102             if (key.equals(mArray[end])) return end;
103         }
104 
105         // Search for a matching key before the index.
106         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
107             if (key.equals(mArray[i])) return i;
108         }
109 
110         // Key not found -- return negative value indicating where a
111         // new entry for this key should go.  We use the end of the
112         // hash chain to reduce the number of array entries that will
113         // need to be copied when inserting.
114         return ~end;
115     }
116 
indexOfNull()117     private int indexOfNull() {
118         final int N = mSize;
119 
120         // Important fast case: if nothing is in here, nothing to look for.
121         if (N == 0) {
122             return ~0;
123         }
124 
125         int index = ContainerHelpers.binarySearch(mHashes, N, 0);
126 
127         // If the hash code wasn't found, then we have no entry for this key.
128         if (index < 0) {
129             return index;
130         }
131 
132         // If the key at the returned index matches, that's what we want.
133         if (null == mArray[index]) {
134             return index;
135         }
136 
137         // Search for a matching key after the index.
138         int end;
139         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
140             if (null == mArray[end]) return end;
141         }
142 
143         // Search for a matching key before the index.
144         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
145             if (null == mArray[i]) return i;
146         }
147 
148         // Key not found -- return negative value indicating where a
149         // new entry for this key should go.  We use the end of the
150         // hash chain to reduce the number of array entries that will
151         // need to be copied when inserting.
152         return ~end;
153     }
154 
allocArrays(final int size)155     private void allocArrays(final int size) {
156         if (size == (BASE_SIZE*2)) {
157             synchronized (ArraySet.class) {
158                 if (mTwiceBaseCache != null) {
159                     final Object[] array = mTwiceBaseCache;
160                     mArray = array;
161                     mTwiceBaseCache = (Object[])array[0];
162                     mHashes = (int[])array[1];
163                     array[0] = array[1] = null;
164                     mTwiceBaseCacheSize--;
165                     if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
166                             + " now have " + mTwiceBaseCacheSize + " entries");
167                     return;
168                 }
169             }
170         } else if (size == BASE_SIZE) {
171             synchronized (ArraySet.class) {
172                 if (mBaseCache != null) {
173                     final Object[] array = mBaseCache;
174                     mArray = array;
175                     mBaseCache = (Object[])array[0];
176                     mHashes = (int[])array[1];
177                     array[0] = array[1] = null;
178                     mBaseCacheSize--;
179                     if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
180                             + " now have " + mBaseCacheSize + " entries");
181                     return;
182                 }
183             }
184         }
185 
186         mHashes = new int[size];
187         mArray = new Object[size];
188     }
189 
freeArrays(final int[] hashes, final Object[] array, final int size)190     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
191         if (hashes.length == (BASE_SIZE*2)) {
192             synchronized (ArraySet.class) {
193                 if (mTwiceBaseCacheSize < CACHE_SIZE) {
194                     array[0] = mTwiceBaseCache;
195                     array[1] = hashes;
196                     for (int i=size-1; i>=2; i--) {
197                         array[i] = null;
198                     }
199                     mTwiceBaseCache = array;
200                     mTwiceBaseCacheSize++;
201                     if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
202                             + " now have " + mTwiceBaseCacheSize + " entries");
203                 }
204             }
205         } else if (hashes.length == BASE_SIZE) {
206             synchronized (ArraySet.class) {
207                 if (mBaseCacheSize < CACHE_SIZE) {
208                     array[0] = mBaseCache;
209                     array[1] = hashes;
210                     for (int i=size-1; i>=2; i--) {
211                         array[i] = null;
212                     }
213                     mBaseCache = array;
214                     mBaseCacheSize++;
215                     if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
216                             + " now have " + mBaseCacheSize + " entries");
217                 }
218             }
219         }
220     }
221 
222     /**
223      * Create a new empty ArraySet.  The default capacity of an array map is 0, and
224      * will grow once items are added to it.
225      */
ArraySet()226     public ArraySet() {
227         mHashes = EmptyArray.INT;
228         mArray = EmptyArray.OBJECT;
229         mSize = 0;
230     }
231 
232     /**
233      * Create a new ArraySet with a given initial capacity.
234      */
ArraySet(int capacity)235     public ArraySet(int capacity) {
236         if (capacity == 0) {
237             mHashes = EmptyArray.INT;
238             mArray = EmptyArray.OBJECT;
239         } else {
240             allocArrays(capacity);
241         }
242         mSize = 0;
243     }
244 
245     /**
246      * Create a new ArraySet with the mappings from the given ArraySet.
247      */
ArraySet(ArraySet<E> set)248     public ArraySet(ArraySet<E> set) {
249         this();
250         if (set != null) {
251             addAll(set);
252         }
253     }
254 
255     /** {@hide} */
ArraySet(Collection<E> set)256     public ArraySet(Collection<E> set) {
257         this();
258         if (set != null) {
259             addAll(set);
260         }
261     }
262 
263     /**
264      * Make the array map empty.  All storage is released.
265      */
266     @Override
clear()267     public void clear() {
268         if (mSize != 0) {
269             freeArrays(mHashes, mArray, mSize);
270             mHashes = EmptyArray.INT;
271             mArray = EmptyArray.OBJECT;
272             mSize = 0;
273         }
274     }
275 
276     /**
277      * Ensure the array map can hold at least <var>minimumCapacity</var>
278      * items.
279      */
ensureCapacity(int minimumCapacity)280     public void ensureCapacity(int minimumCapacity) {
281         if (mHashes.length < minimumCapacity) {
282             final int[] ohashes = mHashes;
283             final Object[] oarray = mArray;
284             allocArrays(minimumCapacity);
285             if (mSize > 0) {
286                 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
287                 System.arraycopy(oarray, 0, mArray, 0, mSize);
288             }
289             freeArrays(ohashes, oarray, mSize);
290         }
291     }
292 
293     /**
294      * Check whether a value exists in the set.
295      *
296      * @param key The value to search for.
297      * @return Returns true if the value exists, else false.
298      */
299     @Override
contains(Object key)300     public boolean contains(Object key) {
301         return indexOf(key) >= 0;
302     }
303 
304     /**
305      * Returns the index of a value in the set.
306      *
307      * @param key The value to search for.
308      * @return Returns the index of the value if it exists, else a negative integer.
309      */
indexOf(Object key)310     public int indexOf(Object key) {
311         return key == null ? indexOfNull() : indexOf(key, key.hashCode());
312     }
313 
314     /**
315      * Return the value at the given index in the array.
316      * @param index The desired index, must be between 0 and {@link #size()}-1.
317      * @return Returns the value stored at the given index.
318      */
valueAt(int index)319     public E valueAt(int index) {
320         return (E)mArray[index];
321     }
322 
323     /**
324      * Return true if the array map contains no items.
325      */
326     @Override
isEmpty()327     public boolean isEmpty() {
328         return mSize <= 0;
329     }
330 
331     /**
332      * Adds the specified object to this set. The set is not modified if it
333      * already contains the object.
334      *
335      * @param value the object to add.
336      * @return {@code true} if this set is modified, {@code false} otherwise.
337      * @throws ClassCastException
338      *             when the class of the object is inappropriate for this set.
339      */
340     @Override
add(E value)341     public boolean add(E value) {
342         final int hash;
343         int index;
344         if (value == null) {
345             hash = 0;
346             index = indexOfNull();
347         } else {
348             hash = value.hashCode();
349             index = indexOf(value, hash);
350         }
351         if (index >= 0) {
352             return false;
353         }
354 
355         index = ~index;
356         if (mSize >= mHashes.length) {
357             final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
358                     : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
359 
360             if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
361 
362             final int[] ohashes = mHashes;
363             final Object[] oarray = mArray;
364             allocArrays(n);
365 
366             if (mHashes.length > 0) {
367                 if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
368                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
369                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
370             }
371 
372             freeArrays(ohashes, oarray, mSize);
373         }
374 
375         if (index < mSize) {
376             if (DEBUG) Log.d(TAG, "add: move " + index + "-" + (mSize-index)
377                     + " to " + (index+1));
378             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
379             System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
380         }
381 
382         mHashes[index] = hash;
383         mArray[index] = value;
384         mSize++;
385         return true;
386     }
387 
388     /**
389      * Perform a {@link #add(Object)} of all values in <var>array</var>
390      * @param array The array whose contents are to be retrieved.
391      */
addAll(ArraySet<? extends E> array)392     public void addAll(ArraySet<? extends E> array) {
393         final int N = array.mSize;
394         ensureCapacity(mSize + N);
395         if (mSize == 0) {
396             if (N > 0) {
397                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
398                 System.arraycopy(array.mArray, 0, mArray, 0, N);
399                 mSize = N;
400             }
401         } else {
402             for (int i=0; i<N; i++) {
403                 add(array.valueAt(i));
404             }
405         }
406     }
407 
408     /**
409      * Removes the specified object from this set.
410      *
411      * @param object the object to remove.
412      * @return {@code true} if this set was modified, {@code false} otherwise.
413      */
414     @Override
remove(Object object)415     public boolean remove(Object object) {
416         final int index = indexOf(object);
417         if (index >= 0) {
418             removeAt(index);
419             return true;
420         }
421         return false;
422     }
423 
424     /**
425      * Remove the key/value mapping at the given index.
426      * @param index The desired index, must be between 0 and {@link #size()}-1.
427      * @return Returns the value that was stored at this index.
428      */
removeAt(int index)429     public E removeAt(int index) {
430         final Object old = mArray[index];
431         if (mSize <= 1) {
432             // Now empty.
433             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
434             freeArrays(mHashes, mArray, mSize);
435             mHashes = EmptyArray.INT;
436             mArray = EmptyArray.OBJECT;
437             mSize = 0;
438         } else {
439             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
440                 // Shrunk enough to reduce size of arrays.  We don't allow it to
441                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
442                 // that and BASE_SIZE.
443                 final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
444 
445                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
446 
447                 final int[] ohashes = mHashes;
448                 final Object[] oarray = mArray;
449                 allocArrays(n);
450 
451                 mSize--;
452                 if (index > 0) {
453                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
454                     System.arraycopy(ohashes, 0, mHashes, 0, index);
455                     System.arraycopy(oarray, 0, mArray, 0, index);
456                 }
457                 if (index < mSize) {
458                     if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
459                             + " to " + index);
460                     System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
461                     System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
462                 }
463             } else {
464                 mSize--;
465                 if (index < mSize) {
466                     if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
467                             + " to " + index);
468                     System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
469                     System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
470                 }
471                 mArray[mSize] = null;
472             }
473         }
474         return (E)old;
475     }
476 
477     /**
478      * Return the number of items in this array map.
479      */
480     @Override
size()481     public int size() {
482         return mSize;
483     }
484 
485     @Override
toArray()486     public Object[] toArray() {
487         Object[] result = new Object[mSize];
488         System.arraycopy(mArray, 0, result, 0, mSize);
489         return result;
490     }
491 
492     @Override
toArray(T[] array)493     public <T> T[] toArray(T[] array) {
494         if (array.length < mSize) {
495             @SuppressWarnings("unchecked") T[] newArray
496                 = (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
497             array = newArray;
498         }
499         System.arraycopy(mArray, 0, array, 0, mSize);
500         if (array.length > mSize) {
501             array[mSize] = null;
502         }
503         return array;
504     }
505 
506     /**
507      * {@inheritDoc}
508      *
509      * <p>This implementation returns false if the object is not a set, or
510      * if the sets have different sizes.  Otherwise, for each value in this
511      * set, it checks to make sure the value also exists in the other set.
512      * If any value doesn't exist, the method returns false; otherwise, it
513      * returns true.
514      */
515     @Override
equals(Object object)516     public boolean equals(Object object) {
517         if (this == object) {
518             return true;
519         }
520         if (object instanceof Set) {
521             Set<?> set = (Set<?>) object;
522             if (size() != set.size()) {
523                 return false;
524             }
525 
526             try {
527                 for (int i=0; i<mSize; i++) {
528                     E mine = valueAt(i);
529                     if (!set.contains(mine)) {
530                         return false;
531                     }
532                 }
533             } catch (NullPointerException ignored) {
534                 return false;
535             } catch (ClassCastException ignored) {
536                 return false;
537             }
538             return true;
539         }
540         return false;
541     }
542 
543     /**
544      * {@inheritDoc}
545      */
546     @Override
hashCode()547     public int hashCode() {
548         final int[] hashes = mHashes;
549         int result = 0;
550         for (int i = 0, s = mSize; i < s; i++) {
551             result += hashes[i];
552         }
553         return result;
554     }
555 
556     /**
557      * {@inheritDoc}
558      *
559      * <p>This implementation composes a string by iterating over its values. If
560      * this set contains itself as a value, the string "(this Set)"
561      * will appear in its place.
562      */
563     @Override
toString()564     public String toString() {
565         if (isEmpty()) {
566             return "{}";
567         }
568 
569         StringBuilder buffer = new StringBuilder(mSize * 14);
570         buffer.append('{');
571         for (int i=0; i<mSize; i++) {
572             if (i > 0) {
573                 buffer.append(", ");
574             }
575             Object value = valueAt(i);
576             if (value != this) {
577                 buffer.append(value);
578             } else {
579                 buffer.append("(this Set)");
580             }
581         }
582         buffer.append('}');
583         return buffer.toString();
584     }
585 
586     // ------------------------------------------------------------------------
587     // Interop with traditional Java containers.  Not as efficient as using
588     // specialized collection APIs.
589     // ------------------------------------------------------------------------
590 
getCollection()591     private MapCollections<E, E> getCollection() {
592         if (mCollections == null) {
593             mCollections = new MapCollections<E, E>() {
594                 @Override
595                 protected int colGetSize() {
596                     return mSize;
597                 }
598 
599                 @Override
600                 protected Object colGetEntry(int index, int offset) {
601                     return mArray[index];
602                 }
603 
604                 @Override
605                 protected int colIndexOfKey(Object key) {
606                     return indexOf(key);
607                 }
608 
609                 @Override
610                 protected int colIndexOfValue(Object value) {
611                     return indexOf(value);
612                 }
613 
614                 @Override
615                 protected Map<E, E> colGetMap() {
616                     throw new UnsupportedOperationException("not a map");
617                 }
618 
619                 @Override
620                 protected void colPut(E key, E value) {
621                     add(key);
622                 }
623 
624                 @Override
625                 protected E colSetValue(int index, E value) {
626                     throw new UnsupportedOperationException("not a map");
627                 }
628 
629                 @Override
630                 protected void colRemoveAt(int index) {
631                     removeAt(index);
632                 }
633 
634                 @Override
635                 protected void colClear() {
636                     clear();
637                 }
638             };
639         }
640         return mCollections;
641     }
642 
643     @Override
iterator()644     public Iterator<E> iterator() {
645         return getCollection().getKeySet().iterator();
646     }
647 
648     @Override
containsAll(Collection<?> collection)649     public boolean containsAll(Collection<?> collection) {
650         Iterator<?> it = collection.iterator();
651         while (it.hasNext()) {
652             if (!contains(it.next())) {
653                 return false;
654             }
655         }
656         return true;
657     }
658 
659     @Override
addAll(Collection<? extends E> collection)660     public boolean addAll(Collection<? extends E> collection) {
661         ensureCapacity(mSize + collection.size());
662         boolean added = false;
663         for (E value : collection) {
664             added |= add(value);
665         }
666         return added;
667     }
668 
669     @Override
removeAll(Collection<?> collection)670     public boolean removeAll(Collection<?> collection) {
671         boolean removed = false;
672         for (Object value : collection) {
673             removed |= remove(value);
674         }
675         return removed;
676     }
677 
678     @Override
retainAll(Collection<?> collection)679     public boolean retainAll(Collection<?> collection) {
680         boolean removed = false;
681         for (int i=mSize-1; i>=0; i--) {
682             if (!collection.contains(mArray[i])) {
683                 removeAt(i);
684                 removed = true;
685             }
686         }
687         return removed;
688     }
689 }
690