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