• 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 public final class ArraySet<E> implements Collection<E>, Set<E> {
47     private static final boolean DEBUG = false;
48     private static final String TAG = "ArraySet";
49 
50     /**
51      * The minimum amount by which the capacity of a ArraySet will increase.
52      * This is tuned to be relatively space-efficient.
53      */
54     private static final int BASE_SIZE = 4;
55 
56     /**
57      * Maximum number of entries to have in array caches.
58      */
59     private static final int CACHE_SIZE = 10;
60 
61     /**
62      * Caches of small array objects to avoid spamming garbage.  The cache
63      * Object[] variable is a pointer to a linked list of array objects.
64      * The first entry in the array is a pointer to the next array in the
65      * list; the second entry is a pointer to the int[] hash code array for it.
66      */
67     static Object[] sBaseCache;
68     static int sBaseCacheSize;
69     static Object[] sTwiceBaseCache;
70     static int sTwiceBaseCacheSize;
71 
72     final boolean mIdentityHashCode;
73     int[] mHashes;
74     Object[] mArray;
75     int mSize;
76     MapCollections<E, E> mCollections;
77 
indexOf(Object key, int hash)78     private int indexOf(Object key, int hash) {
79         final int N = mSize;
80 
81         // Important fast case: if nothing is in here, nothing to look for.
82         if (N == 0) {
83             return ~0;
84         }
85 
86         int index = ContainerHelpers.binarySearch(mHashes, N, hash);
87 
88         // If the hash code wasn't found, then we have no entry for this key.
89         if (index < 0) {
90             return index;
91         }
92 
93         // If the key at the returned index matches, that's what we want.
94         if (key.equals(mArray[index])) {
95             return index;
96         }
97 
98         // Search for a matching key after the index.
99         int end;
100         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
101             if (key.equals(mArray[end])) return end;
102         }
103 
104         // Search for a matching key before the index.
105         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
106             if (key.equals(mArray[i])) return i;
107         }
108 
109         // Key not found -- return negative value indicating where a
110         // new entry for this key should go.  We use the end of the
111         // hash chain to reduce the number of array entries that will
112         // need to be copied when inserting.
113         return ~end;
114     }
115 
indexOfNull()116     private int indexOfNull() {
117         final int N = mSize;
118 
119         // Important fast case: if nothing is in here, nothing to look for.
120         if (N == 0) {
121             return ~0;
122         }
123 
124         int index = ContainerHelpers.binarySearch(mHashes, N, 0);
125 
126         // If the hash code wasn't found, then we have no entry for this key.
127         if (index < 0) {
128             return index;
129         }
130 
131         // If the key at the returned index matches, that's what we want.
132         if (null == mArray[index]) {
133             return index;
134         }
135 
136         // Search for a matching key after the index.
137         int end;
138         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
139             if (null == mArray[end]) return end;
140         }
141 
142         // Search for a matching key before the index.
143         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
144             if (null == mArray[i]) return i;
145         }
146 
147         // Key not found -- return negative value indicating where a
148         // new entry for this key should go.  We use the end of the
149         // hash chain to reduce the number of array entries that will
150         // need to be copied when inserting.
151         return ~end;
152     }
153 
allocArrays(final int size)154     private void allocArrays(final int size) {
155         if (size == (BASE_SIZE * 2)) {
156             synchronized (ArraySet.class) {
157                 if (sTwiceBaseCache != null) {
158                     final Object[] array = sTwiceBaseCache;
159                     try {
160                         mArray = array;
161                         sTwiceBaseCache = (Object[]) array[0];
162                         mHashes = (int[]) array[1];
163                         array[0] = array[1] = null;
164                         sTwiceBaseCacheSize--;
165                         if (DEBUG) {
166                             Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
167                                     + sTwiceBaseCacheSize + " entries");
168                     }
169                     return;
170                     } catch (ClassCastException e) {
171                     }
172                     // Whoops!  Someone trampled the array (probably due to not protecting
173                     // their access with a lock).  Our cache is corrupt; report and give up.
174                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
175                             + " [1]=" + array[1]);
176                     sTwiceBaseCache = null;
177                     sTwiceBaseCacheSize = 0;
178                 }
179             }
180         } else if (size == BASE_SIZE) {
181             synchronized (ArraySet.class) {
182                 if (sBaseCache != null) {
183                     final Object[] array = sBaseCache;
184                     try {
185                         mArray = array;
186                         sBaseCache = (Object[]) array[0];
187                         mHashes = (int[]) array[1];
188                         array[0] = array[1] = null;
189                         sBaseCacheSize--;
190                         if (DEBUG) {
191                             Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
192                                     + " entries");
193                         }
194                         return;
195                     } catch (ClassCastException e) {
196                     }
197                     // Whoops!  Someone trampled the array (probably due to not protecting
198                     // their access with a lock).  Our cache is corrupt; report and give up.
199                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
200                             + " [1]=" + array[1]);
201                     sBaseCache = null;
202                     sBaseCacheSize = 0;
203                 }
204             }
205         }
206 
207         mHashes = new int[size];
208         mArray = new Object[size];
209     }
210 
freeArrays(final int[] hashes, final Object[] array, final int size)211     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
212         if (hashes.length == (BASE_SIZE * 2)) {
213             synchronized (ArraySet.class) {
214                 if (sTwiceBaseCacheSize < CACHE_SIZE) {
215                     array[0] = sTwiceBaseCache;
216                     array[1] = hashes;
217                     for (int i = size - 1; i >= 2; i--) {
218                         array[i] = null;
219                     }
220                     sTwiceBaseCache = array;
221                     sTwiceBaseCacheSize++;
222                     if (DEBUG) {
223                         Log.d(TAG, "Storing 2x cache " + array + " now have " + sTwiceBaseCacheSize
224                                 + " entries");
225                     }
226                 }
227             }
228         } else if (hashes.length == BASE_SIZE) {
229             synchronized (ArraySet.class) {
230                 if (sBaseCacheSize < CACHE_SIZE) {
231                     array[0] = sBaseCache;
232                     array[1] = hashes;
233                     for (int i = size - 1; i >= 2; i--) {
234                         array[i] = null;
235                     }
236                     sBaseCache = array;
237                     sBaseCacheSize++;
238                     if (DEBUG) {
239                         Log.d(TAG, "Storing 1x cache " + array + " now have "
240                                 + sBaseCacheSize + " entries");
241                     }
242                 }
243             }
244         }
245     }
246 
247     /**
248      * Create a new empty ArraySet.  The default capacity of an array map is 0, and
249      * will grow once items are added to it.
250      */
ArraySet()251     public ArraySet() {
252         this(0, false);
253     }
254 
255     /**
256      * Create a new ArraySet with a given initial capacity.
257      */
ArraySet(int capacity)258     public ArraySet(int capacity) {
259         this(capacity, false);
260     }
261 
262     /** {@hide} */
ArraySet(int capacity, boolean identityHashCode)263     public ArraySet(int capacity, boolean identityHashCode) {
264         mIdentityHashCode = identityHashCode;
265         if (capacity == 0) {
266             mHashes = EmptyArray.INT;
267             mArray = EmptyArray.OBJECT;
268         } else {
269             allocArrays(capacity);
270         }
271         mSize = 0;
272     }
273 
274     /**
275      * Create a new ArraySet with the mappings from the given ArraySet.
276      */
ArraySet(ArraySet<E> set)277     public ArraySet(ArraySet<E> set) {
278         this();
279         if (set != null) {
280             addAll(set);
281         }
282     }
283 
284     /** {@hide} */
ArraySet(Collection<E> set)285     public ArraySet(Collection<E> set) {
286         this();
287         if (set != null) {
288             addAll(set);
289         }
290     }
291 
292     /**
293      * Make the array map empty.  All storage is released.
294      */
295     @Override
clear()296     public void clear() {
297         if (mSize != 0) {
298             freeArrays(mHashes, mArray, mSize);
299             mHashes = EmptyArray.INT;
300             mArray = EmptyArray.OBJECT;
301             mSize = 0;
302         }
303     }
304 
305     /**
306      * Ensure the array map can hold at least <var>minimumCapacity</var>
307      * items.
308      */
ensureCapacity(int minimumCapacity)309     public void ensureCapacity(int minimumCapacity) {
310         if (mHashes.length < minimumCapacity) {
311             final int[] ohashes = mHashes;
312             final Object[] oarray = mArray;
313             allocArrays(minimumCapacity);
314             if (mSize > 0) {
315                 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
316                 System.arraycopy(oarray, 0, mArray, 0, mSize);
317             }
318             freeArrays(ohashes, oarray, mSize);
319         }
320     }
321 
322     /**
323      * Check whether a value exists in the set.
324      *
325      * @param key The value to search for.
326      * @return Returns true if the value exists, else false.
327      */
328     @Override
contains(Object key)329     public boolean contains(Object key) {
330         return indexOf(key) >= 0;
331     }
332 
333     /**
334      * Returns the index of a value in the set.
335      *
336      * @param key The value to search for.
337      * @return Returns the index of the value if it exists, else a negative integer.
338      */
indexOf(Object key)339     public int indexOf(Object key) {
340         return key == null ? indexOfNull()
341                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
342     }
343 
344     /**
345      * Return the value at the given index in the array.
346      * @param index The desired index, must be between 0 and {@link #size()}-1.
347      * @return Returns the value stored at the given index.
348      */
valueAt(int index)349     public E valueAt(int index) {
350         return (E) mArray[index];
351     }
352 
353     /**
354      * Return true if the array map contains no items.
355      */
356     @Override
isEmpty()357     public boolean isEmpty() {
358         return mSize <= 0;
359     }
360 
361     /**
362      * Adds the specified object to this set. The set is not modified if it
363      * already contains the object.
364      *
365      * @param value the object to add.
366      * @return {@code true} if this set is modified, {@code false} otherwise.
367      * @throws ClassCastException
368      *             when the class of the object is inappropriate for this set.
369      */
370     @Override
add(E value)371     public boolean add(E value) {
372         final int hash;
373         int index;
374         if (value == null) {
375             hash = 0;
376             index = indexOfNull();
377         } else {
378             hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
379             index = indexOf(value, hash);
380         }
381         if (index >= 0) {
382             return false;
383         }
384 
385         index = ~index;
386         if (mSize >= mHashes.length) {
387             final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
388                     : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
389 
390             if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
391 
392             final int[] ohashes = mHashes;
393             final Object[] oarray = mArray;
394             allocArrays(n);
395 
396             if (mHashes.length > 0) {
397                 if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
398                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
399                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
400             }
401 
402             freeArrays(ohashes, oarray, mSize);
403         }
404 
405         if (index < mSize) {
406             if (DEBUG) {
407                 Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
408             }
409             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
410             System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
411         }
412 
413         mHashes[index] = hash;
414         mArray[index] = value;
415         mSize++;
416         return true;
417     }
418 
419     /**
420      * Special fast path for appending items to the end of the array without validation.
421      * The array must already be large enough to contain the item.
422      * @hide
423      */
append(E value)424     public void append(E value) {
425         final int index = mSize;
426         final int hash = value == null ? 0
427                 : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
428         if (index >= mHashes.length) {
429             throw new IllegalStateException("Array is full");
430         }
431         if (index > 0 && mHashes[index - 1] > hash) {
432             // Cannot optimize since it would break the sorted order - fallback to add()
433             if (DEBUG) {
434                 RuntimeException e = new RuntimeException("here");
435                 e.fillInStackTrace();
436                 Log.w(TAG, "New hash " + hash
437                         + " is before end of array hash " + mHashes[index - 1]
438                         + " at index " + index, e);
439             }
440             add(value);
441             return;
442         }
443         mSize = index + 1;
444         mHashes[index] = hash;
445         mArray[index] = value;
446     }
447 
448     /**
449      * Perform a {@link #add(Object)} of all values in <var>array</var>
450      * @param array The array whose contents are to be retrieved.
451      */
addAll(ArraySet<? extends E> array)452     public void addAll(ArraySet<? extends E> array) {
453         final int N = array.mSize;
454         ensureCapacity(mSize + N);
455         if (mSize == 0) {
456             if (N > 0) {
457                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
458                 System.arraycopy(array.mArray, 0, mArray, 0, N);
459                 mSize = N;
460             }
461         } else {
462             for (int i = 0; i < N; i++) {
463                 add(array.valueAt(i));
464             }
465         }
466     }
467 
468     /**
469      * Removes the specified object from this set.
470      *
471      * @param object the object to remove.
472      * @return {@code true} if this set was modified, {@code false} otherwise.
473      */
474     @Override
remove(Object object)475     public boolean remove(Object object) {
476         final int index = indexOf(object);
477         if (index >= 0) {
478             removeAt(index);
479             return true;
480         }
481         return false;
482     }
483 
484     /**
485      * Remove the key/value mapping at the given index.
486      * @param index The desired index, must be between 0 and {@link #size()}-1.
487      * @return Returns the value that was stored at this index.
488      */
removeAt(int index)489     public E removeAt(int index) {
490         final Object old = mArray[index];
491         if (mSize <= 1) {
492             // Now empty.
493             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
494             freeArrays(mHashes, mArray, mSize);
495             mHashes = EmptyArray.INT;
496             mArray = EmptyArray.OBJECT;
497             mSize = 0;
498         } else {
499             if (mHashes.length > (BASE_SIZE * 2) && mSize < mHashes.length / 3) {
500                 // Shrunk enough to reduce size of arrays.  We don't allow it to
501                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
502                 // that and BASE_SIZE.
503                 final int n = mSize > (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (BASE_SIZE * 2);
504 
505                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
506 
507                 final int[] ohashes = mHashes;
508                 final Object[] oarray = mArray;
509                 allocArrays(n);
510 
511                 mSize--;
512                 if (index > 0) {
513                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
514                     System.arraycopy(ohashes, 0, mHashes, 0, index);
515                     System.arraycopy(oarray, 0, mArray, 0, index);
516                 }
517                 if (index < mSize) {
518                     if (DEBUG) {
519                         Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
520                                 + " to " + index);
521                     }
522                     System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
523                     System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
524                 }
525             } else {
526                 mSize--;
527                 if (index < mSize) {
528                     if (DEBUG) {
529                         Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
530                     }
531                     System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
532                     System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
533                 }
534                 mArray[mSize] = null;
535             }
536         }
537         return (E) old;
538     }
539 
540     /**
541      * Perform a {@link #remove(Object)} of all values in <var>array</var>
542      * @param array The array whose contents are to be removed.
543      */
removeAll(ArraySet<? extends E> array)544     public boolean removeAll(ArraySet<? extends E> array) {
545         // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
546         //       pass, use the property that the sets are sorted by hash to make this linear passes
547         //       (except for hash collisions, which means worst case still n*m), then do one
548         //       collection pass into a new array. This avoids binary searches and excessive memcpy.
549         final int N = array.mSize;
550 
551         // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
552         //       the single results, compare size before and after.
553         final int originalSize = mSize;
554         for (int i = 0; i < N; i++) {
555             remove(array.valueAt(i));
556         }
557         return originalSize != mSize;
558     }
559 
560     /**
561      * Return the number of items in this array map.
562      */
563     @Override
size()564     public int size() {
565         return mSize;
566     }
567 
568     @Override
toArray()569     public Object[] toArray() {
570         Object[] result = new Object[mSize];
571         System.arraycopy(mArray, 0, result, 0, mSize);
572         return result;
573     }
574 
575     @Override
toArray(T[] array)576     public <T> T[] toArray(T[] array) {
577         if (array.length < mSize) {
578             @SuppressWarnings("unchecked") T[] newArray =
579                     (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
580             array = newArray;
581         }
582         System.arraycopy(mArray, 0, array, 0, mSize);
583         if (array.length > mSize) {
584             array[mSize] = null;
585         }
586         return array;
587     }
588 
589     /**
590      * {@inheritDoc}
591      *
592      * <p>This implementation returns false if the object is not a set, or
593      * if the sets have different sizes.  Otherwise, for each value in this
594      * set, it checks to make sure the value also exists in the other set.
595      * If any value doesn't exist, the method returns false; otherwise, it
596      * returns true.
597      */
598     @Override
equals(Object object)599     public boolean equals(Object object) {
600         if (this == object) {
601             return true;
602         }
603         if (object instanceof Set) {
604             Set<?> set = (Set<?>) object;
605             if (size() != set.size()) {
606                 return false;
607             }
608 
609             try {
610                 for (int i = 0; i < mSize; i++) {
611                     E mine = valueAt(i);
612                     if (!set.contains(mine)) {
613                         return false;
614                     }
615                 }
616             } catch (NullPointerException ignored) {
617                 return false;
618             } catch (ClassCastException ignored) {
619                 return false;
620             }
621             return true;
622         }
623         return false;
624     }
625 
626     /**
627      * {@inheritDoc}
628      */
629     @Override
hashCode()630     public int hashCode() {
631         final int[] hashes = mHashes;
632         int result = 0;
633         for (int i = 0, s = mSize; i < s; i++) {
634             result += hashes[i];
635         }
636         return result;
637     }
638 
639     /**
640      * {@inheritDoc}
641      *
642      * <p>This implementation composes a string by iterating over its values. If
643      * this set contains itself as a value, the string "(this Set)"
644      * will appear in its place.
645      */
646     @Override
toString()647     public String toString() {
648         if (isEmpty()) {
649             return "{}";
650         }
651 
652         StringBuilder buffer = new StringBuilder(mSize * 14);
653         buffer.append('{');
654         for (int i = 0; i < mSize; i++) {
655             if (i > 0) {
656                 buffer.append(", ");
657             }
658             Object value = valueAt(i);
659             if (value != this) {
660                 buffer.append(value);
661             } else {
662                 buffer.append("(this Set)");
663             }
664         }
665         buffer.append('}');
666         return buffer.toString();
667     }
668 
669     // ------------------------------------------------------------------------
670     // Interop with traditional Java containers.  Not as efficient as using
671     // specialized collection APIs.
672     // ------------------------------------------------------------------------
673 
getCollection()674     private MapCollections<E, E> getCollection() {
675         if (mCollections == null) {
676             mCollections = new MapCollections<E, E>() {
677                 @Override
678                 protected int colGetSize() {
679                     return mSize;
680                 }
681 
682                 @Override
683                 protected Object colGetEntry(int index, int offset) {
684                     return mArray[index];
685                 }
686 
687                 @Override
688                 protected int colIndexOfKey(Object key) {
689                     return indexOf(key);
690                 }
691 
692                 @Override
693                 protected int colIndexOfValue(Object value) {
694                     return indexOf(value);
695                 }
696 
697                 @Override
698                 protected Map<E, E> colGetMap() {
699                     throw new UnsupportedOperationException("not a map");
700                 }
701 
702                 @Override
703                 protected void colPut(E key, E value) {
704                     add(key);
705                 }
706 
707                 @Override
708                 protected E colSetValue(int index, E value) {
709                     throw new UnsupportedOperationException("not a map");
710                 }
711 
712                 @Override
713                 protected void colRemoveAt(int index) {
714                     removeAt(index);
715                 }
716 
717                 @Override
718                 protected void colClear() {
719                     clear();
720                 }
721             };
722         }
723         return mCollections;
724     }
725 
726     /**
727      * Return an {@link java.util.Iterator} over all values in the set.
728      *
729      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
730      * requires generating a number of temporary objects and allocates additional state
731      * information associated with the container that will remain for the life of the container.</p>
732      */
733     @Override
iterator()734     public Iterator<E> iterator() {
735         return getCollection().getKeySet().iterator();
736     }
737 
738     /**
739      * Determine if the array set contains all of the values in the given collection.
740      * @param collection The collection whose contents are to be checked against.
741      * @return Returns true if this array set contains a value for every entry
742      * in <var>collection</var>, else returns false.
743      */
744     @Override
containsAll(Collection<?> collection)745     public boolean containsAll(Collection<?> collection) {
746         Iterator<?> it = collection.iterator();
747         while (it.hasNext()) {
748             if (!contains(it.next())) {
749                 return false;
750             }
751         }
752         return true;
753     }
754 
755     /**
756      * Perform an {@link #add(Object)} of all values in <var>collection</var>
757      * @param collection The collection whose contents are to be retrieved.
758      */
759     @Override
addAll(Collection<? extends E> collection)760     public boolean addAll(Collection<? extends E> collection) {
761         ensureCapacity(mSize + collection.size());
762         boolean added = false;
763         for (E value : collection) {
764             added |= add(value);
765         }
766         return added;
767     }
768 
769     /**
770      * Remove all values in the array set that exist in the given collection.
771      * @param collection The collection whose contents are to be used to remove values.
772      * @return Returns true if any values were removed from the array set, else false.
773      */
774     @Override
removeAll(Collection<?> collection)775     public boolean removeAll(Collection<?> collection) {
776         boolean removed = false;
777         for (Object value : collection) {
778             removed |= remove(value);
779         }
780         return removed;
781     }
782 
783     /**
784      * Remove all values in the array set that do <b>not</b> exist in the given collection.
785      * @param collection The collection whose contents are to be used to determine which
786      * values to keep.
787      * @return Returns true if any values were removed from the array set, else false.
788      */
789     @Override
retainAll(Collection<?> collection)790     public boolean retainAll(Collection<?> collection) {
791         boolean removed = false;
792         for (int i = mSize - 1; i >= 0; i--) {
793             if (!collection.contains(mArray[i])) {
794                 removeAt(i);
795                 removed = true;
796             }
797         }
798         return removed;
799     }
800 }
801