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