Coverage Summary for Class: CompactHashMap (com.google.common.collect)

Class Method, % Line, %
CompactHashMap 0% (0/47) 0% (0/265)
CompactHashMap$1 0% (0/2) 0% (0/2)
CompactHashMap$2 0% (0/2) 0% (0/2)
CompactHashMap$3 0% (0/2) 0% (0/2)
CompactHashMap$EntrySetView 0% (0/7) 0% (0/34)
CompactHashMap$Itr 0% (0/7) 0% (0/21)
CompactHashMap$KeySetView 0% (0/7) 0% (0/33)
CompactHashMap$MapEntry 0% (0/5) 0% (0/23)
CompactHashMap$ValuesView 0% (0/6) 0% (0/29)
Total 0% (0/85) 0% (0/411)


1 /* 2  * Copyright (C) 2012 The Guava Authors 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 com.google.common.collect; 18  19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.collect.CollectPreconditions.checkRemove; 21 import static com.google.common.collect.CompactHashing.UNSET; 22 import static com.google.common.collect.Hashing.smearedHash; 23  24 import com.google.common.annotations.GwtIncompatible; 25 import com.google.common.annotations.VisibleForTesting; 26 import com.google.common.base.Objects; 27 import com.google.common.base.Preconditions; 28 import com.google.common.primitives.Ints; 29 import com.google.errorprone.annotations.CanIgnoreReturnValue; 30 import com.google.j2objc.annotations.WeakOuter; 31 import java.io.IOException; 32 import java.io.InvalidObjectException; 33 import java.io.ObjectInputStream; 34 import java.io.ObjectOutputStream; 35 import java.io.Serializable; 36 import java.util.AbstractMap; 37 import java.util.Arrays; 38 import java.util.Collection; 39 import java.util.ConcurrentModificationException; 40 import java.util.Iterator; 41 import java.util.LinkedHashMap; 42 import java.util.Map; 43 import java.util.NoSuchElementException; 44 import java.util.Set; 45 import java.util.Spliterator; 46 import java.util.Spliterators; 47 import java.util.function.BiConsumer; 48 import java.util.function.BiFunction; 49 import java.util.function.Consumer; 50 import org.checkerframework.checker.nullness.qual.Nullable; 51  52 /** 53  * CompactHashMap is an implementation of a Map. All optional operations (put and remove) are 54  * supported. Null keys and values are supported. 55  * 56  * <p>{@code containsKey(k)}, {@code put(k, v)} and {@code remove(k)} are all (expected and 57  * amortized) constant time operations. Expected in the hashtable sense (depends on the hash 58  * function doing a good job of distributing the elements to the buckets to a distribution not far 59  * from uniform), and amortized since some operations can trigger a hash table resize. 60  * 61  * <p>Unlike {@code java.util.HashMap}, iteration is only proportional to the actual {@code size()}, 62  * which is optimal, and <i>not</i> the size of the internal hashtable, which could be much larger 63  * than {@code size()}. Furthermore, this structure places significantly reduced load on the garbage 64  * collector by only using a constant number of internal objects. 65  * 66  * <p>If there are no removals, then iteration order for the {@link #entrySet}, {@link #keySet}, and 67  * {@link #values} views is the same as insertion order. Any removal invalidates any ordering 68  * guarantees. 69  * 70  * <p>This class should not be assumed to be universally superior to {@code java.util.HashMap}. 71  * Generally speaking, this class reduces object allocation and memory consumption at the price of 72  * moderately increased constant factors of CPU. Only use this class when there is a specific reason 73  * to prioritize memory over CPU. 74  * 75  * @author Louis Wasserman 76  * @author Jon Noack 77  */ 78 @GwtIncompatible // not worth using in GWT for now 79 class CompactHashMap<K, V> extends AbstractMap<K, V> implements Serializable { 80  /* 81  * TODO: Make this a drop-in replacement for j.u. versions, actually drop them in, and test the 82  * world. Figure out what sort of space-time tradeoff we're actually going to get here with the 83  * *Map variants. This class is particularly hard to benchmark, because the benefit is not only in 84  * less allocation, but also having the GC do less work to scan the heap because of fewer 85  * references, which is particularly hard to quantify. 86  */ 87  88  /** Creates an empty {@code CompactHashMap} instance. */ 89  public static <K, V> CompactHashMap<K, V> create() { 90  return new CompactHashMap<>(); 91  } 92  93  /** 94  * Creates a {@code CompactHashMap} instance, with a high enough "initial capacity" that it 95  * <i>should</i> hold {@code expectedSize} elements without growth. 96  * 97  * @param expectedSize the number of elements you expect to add to the returned set 98  * @return a new, empty {@code CompactHashMap} with enough capacity to hold {@code expectedSize} 99  * elements without resizing 100  * @throws IllegalArgumentException if {@code expectedSize} is negative 101  */ 102  public static <K, V> CompactHashMap<K, V> createWithExpectedSize(int expectedSize) { 103  return new CompactHashMap<>(expectedSize); 104  } 105  106  private static final Object NOT_FOUND = new Object(); 107  108  /** 109  * Maximum allowed false positive probability of detecting a hash flooding attack given random 110  * input. 111  */ 112  @VisibleForTesting( 113  ) 114  static final double HASH_FLOODING_FPP = 0.001; 115  116  /** 117  * Maximum allowed length of a hash table bucket before falling back to a j.u.LinkedHashMap-based 118  * implementation. Experimentally determined. 119  */ 120  private static final int MAX_HASH_BUCKET_LENGTH = 9; 121  122  /** 123  * The hashtable object. This can be either: 124  * 125  * <ul> 126  * <li>a byte[], short[], or int[], with size a power of two, created by 127  * CompactHashing.createTable, whose values are either 128  * <ul> 129  * <li>UNSET, meaning "null pointer" 130  * <li>one plus an index into the keys, values, and entries arrays 131  * </ul> 132  * <li>another java.util.Map delegate implementation. In most modern JDKs, normal java.util hash 133  * collections intelligently fall back to a binary search tree if hash table collisions are 134  * detected. Rather than going to all the trouble of reimplementing this ourselves, we 135  * simply switch over to use the JDK implementation wholesale if probable hash flooding is 136  * detected, sacrificing the compactness guarantee in very rare cases in exchange for much 137  * more reliable worst-case behavior. 138  * <li>null, if no entries have yet been added to the map 139  * </ul> 140  */ 141  @Nullable private transient Object table; 142  143  /** 144  * Contains the logical entries, in the range of [0, size()). The high bits of each int are the 145  * part of the smeared hash of the key not covered by the hashtable mask, whereas the low bits are 146  * the "next" pointer (pointing to the next entry in the bucket chain), which will always be less 147  * than or equal to the hashtable mask. 148  * 149  * <pre> 150  * hash = aaaaaaaa 151  * mask = 0000ffff 152  * next = 0000bbbb 153  * entry = aaaabbbb 154  * </pre> 155  * 156  * <p>The pointers in [size(), entries.length) are all "null" (UNSET). 157  */ 158  @VisibleForTesting transient int @Nullable [] entries; 159  160  /** 161  * The keys of the entries in the map, in the range of [0, size()). The keys in [size(), 162  * keys.length) are all {@code null}. 163  */ 164  @VisibleForTesting transient Object @Nullable [] keys; 165  166  /** 167  * The values of the entries in the map, in the range of [0, size()). The values in [size(), 168  * values.length) are all {@code null}. 169  */ 170  @VisibleForTesting transient Object @Nullable [] values; 171  172  /** 173  * Keeps track of metadata like the number of hash table bits and modifications of this data 174  * structure (to make it possible to throw ConcurrentModificationException in the iterator). Note 175  * that we choose not to make this volatile, so we do less of a "best effort" to track such 176  * errors, for better performance. 177  */ 178  private transient int metadata; 179  180  /** The number of elements contained in the set. */ 181  private transient int size; 182  183  /** Constructs a new empty instance of {@code CompactHashMap}. */ 184  CompactHashMap() { 185  init(CompactHashing.DEFAULT_SIZE); 186  } 187  188  /** 189  * Constructs a new instance of {@code CompactHashMap} with the specified capacity. 190  * 191  * @param expectedSize the initial capacity of this {@code CompactHashMap}. 192  */ 193  CompactHashMap(int expectedSize) { 194  init(expectedSize); 195  } 196  197  /** Pseudoconstructor for serialization support. */ 198  void init(int expectedSize) { 199  Preconditions.checkArgument(expectedSize >= 0, "Expected size must be >= 0"); 200  201  // Save expectedSize for use in allocArrays() 202  this.metadata = Ints.constrainToRange(expectedSize, 1, CompactHashing.MAX_SIZE); 203  } 204  205  /** Returns whether arrays need to be allocated. */ 206  @VisibleForTesting 207  boolean needsAllocArrays() { 208  return table == null; 209  } 210  211  /** Handle lazy allocation of arrays. */ 212  @CanIgnoreReturnValue 213  int allocArrays() { 214  Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); 215  216  int expectedSize = metadata; 217  int buckets = CompactHashing.tableSize(expectedSize); 218  this.table = CompactHashing.createTable(buckets); 219  setHashTableMask(buckets - 1); 220  221  this.entries = new int[expectedSize]; 222  this.keys = new Object[expectedSize]; 223  this.values = new Object[expectedSize]; 224  225  return expectedSize; 226  } 227  228  @SuppressWarnings("unchecked") 229  @VisibleForTesting 230  @Nullable 231  Map<K, V> delegateOrNull() { 232  if (table instanceof Map) { 233  return (Map<K, V>) table; 234  } 235  return null; 236  } 237  238  Map<K, V> createHashFloodingResistantDelegate(int tableSize) { 239  return new LinkedHashMap<>(tableSize, 1.0f); 240  } 241  242  @SuppressWarnings("unchecked") 243  @VisibleForTesting 244  @CanIgnoreReturnValue 245  Map<K, V> convertToHashFloodingResistantImplementation() { 246  Map<K, V> newDelegate = createHashFloodingResistantDelegate(hashTableMask() + 1); 247  for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { 248  newDelegate.put((K) keys[i], (V) values[i]); 249  } 250  this.table = newDelegate; 251  this.entries = null; 252  this.keys = null; 253  this.values = null; 254  incrementModCount(); 255  return newDelegate; 256  } 257  258  /** Stores the hash table mask as the number of bits needed to represent an index. */ 259  private void setHashTableMask(int mask) { 260  int hashTableBits = Integer.SIZE - Integer.numberOfLeadingZeros(mask); 261  metadata = 262  CompactHashing.maskCombine(metadata, hashTableBits, CompactHashing.HASH_TABLE_BITS_MASK); 263  } 264  265  /** Gets the hash table mask using the stored number of hash table bits. */ 266  private int hashTableMask() { 267  return (1 << (metadata & CompactHashing.HASH_TABLE_BITS_MASK)) - 1; 268  } 269  270  void incrementModCount() { 271  metadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; 272  } 273  274  /** 275  * Mark an access of the specified entry. Used only in {@code CompactLinkedHashMap} for LRU 276  * ordering. 277  */ 278  void accessEntry(int index) { 279  // no-op by default 280  } 281  282  @CanIgnoreReturnValue 283  @Override 284  public @Nullable V put(@Nullable K key, @Nullable V value) { 285  if (needsAllocArrays()) { 286  allocArrays(); 287  } 288  @Nullable Map<K, V> delegate = delegateOrNull(); 289  if (delegate != null) { 290  return delegate.put(key, value); 291  } 292  int[] entries = this.entries; 293  Object[] keys = this.keys; 294  Object[] values = this.values; 295  296  int newEntryIndex = this.size; // current size, and pointer to the entry to be appended 297  int newSize = newEntryIndex + 1; 298  int hash = smearedHash(key); 299  int mask = hashTableMask(); 300  int tableIndex = hash & mask; 301  int next = CompactHashing.tableGet(table, tableIndex); 302  if (next == UNSET) { // uninitialized bucket 303  if (newSize > mask) { 304  // Resize and add new entry 305  mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); 306  } else { 307  CompactHashing.tableSet(table, tableIndex, newEntryIndex + 1); 308  } 309  } else { 310  int entryIndex; 311  int entry; 312  int hashPrefix = CompactHashing.getHashPrefix(hash, mask); 313  int bucketLength = 0; 314  do { 315  entryIndex = next - 1; 316  entry = entries[entryIndex]; 317  if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix 318  && Objects.equal(key, keys[entryIndex])) { 319  @SuppressWarnings("unchecked") // known to be a V 320  @Nullable 321  V oldValue = (V) values[entryIndex]; 322  323  values[entryIndex] = value; 324  accessEntry(entryIndex); 325  return oldValue; 326  } 327  next = CompactHashing.getNext(entry, mask); 328  bucketLength++; 329  } while (next != UNSET); 330  331  if (bucketLength >= MAX_HASH_BUCKET_LENGTH) { 332  return convertToHashFloodingResistantImplementation().put(key, value); 333  } 334  335  if (newSize > mask) { 336  // Resize and add new entry 337  mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); 338  } else { 339  entries[entryIndex] = CompactHashing.maskCombine(entry, newEntryIndex + 1, mask); 340  } 341  } 342  resizeMeMaybe(newSize); 343  insertEntry(newEntryIndex, key, value, hash, mask); 344  this.size = newSize; 345  incrementModCount(); 346  return null; 347  } 348  349  /** 350  * Creates a fresh entry with the specified object at the specified position in the entry arrays. 351  */ 352  void insertEntry(int entryIndex, @Nullable K key, @Nullable V value, int hash, int mask) { 353  this.entries[entryIndex] = CompactHashing.maskCombine(hash, UNSET, mask); 354  this.keys[entryIndex] = key; 355  this.values[entryIndex] = value; 356  } 357  358  /** Resizes the entries storage if necessary. */ 359  private void resizeMeMaybe(int newSize) { 360  int entriesSize = entries.length; 361  if (newSize > entriesSize) { 362  // 1.5x but round up to nearest odd (this is optimal for memory consumption on Android) 363  int newCapacity = 364  Math.min(CompactHashing.MAX_SIZE, (entriesSize + Math.max(1, entriesSize >>> 1)) | 1); 365  if (newCapacity != entriesSize) { 366  resizeEntries(newCapacity); 367  } 368  } 369  } 370  371  /** 372  * Resizes the internal entries array to the specified capacity, which may be greater or less than 373  * the current capacity. 374  */ 375  void resizeEntries(int newCapacity) { 376  this.entries = Arrays.copyOf(entries, newCapacity); 377  this.keys = Arrays.copyOf(keys, newCapacity); 378  this.values = Arrays.copyOf(values, newCapacity); 379  } 380  381  @CanIgnoreReturnValue 382  private int resizeTable(int mask, int newCapacity, int targetHash, int targetEntryIndex) { 383  Object newTable = CompactHashing.createTable(newCapacity); 384  int newMask = newCapacity - 1; 385  386  if (targetEntryIndex != UNSET) { 387  // Add target first; it must be last in the chain because its entry hasn't yet been created 388  CompactHashing.tableSet(newTable, targetHash & newMask, targetEntryIndex + 1); 389  } 390  391  Object table = this.table; 392  int[] entries = this.entries; 393  394  // Loop over current hashtable 395  for (int tableIndex = 0; tableIndex <= mask; tableIndex++) { 396  int next = CompactHashing.tableGet(table, tableIndex); 397  while (next != UNSET) { 398  int entryIndex = next - 1; 399  int entry = entries[entryIndex]; 400  401  // Rebuild hash using entry hashPrefix and tableIndex ("hashSuffix") 402  int hash = CompactHashing.getHashPrefix(entry, mask) | tableIndex; 403  404  int newTableIndex = hash & newMask; 405  int newNext = CompactHashing.tableGet(newTable, newTableIndex); 406  CompactHashing.tableSet(newTable, newTableIndex, next); 407  entries[entryIndex] = CompactHashing.maskCombine(hash, newNext, newMask); 408  409  next = CompactHashing.getNext(entry, mask); 410  } 411  } 412  413  this.table = newTable; 414  setHashTableMask(newMask); 415  return newMask; 416  } 417  418  private int indexOf(@Nullable Object key) { 419  if (needsAllocArrays()) { 420  return -1; 421  } 422  int hash = smearedHash(key); 423  int mask = hashTableMask(); 424  int next = CompactHashing.tableGet(table, hash & mask); 425  if (next == UNSET) { 426  return -1; 427  } 428  int hashPrefix = CompactHashing.getHashPrefix(hash, mask); 429  do { 430  int entryIndex = next - 1; 431  int entry = entries[entryIndex]; 432  if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix 433  && Objects.equal(key, keys[entryIndex])) { 434  return entryIndex; 435  } 436  next = CompactHashing.getNext(entry, mask); 437  } while (next != UNSET); 438  return -1; 439  } 440  441  @Override 442  public boolean containsKey(@Nullable Object key) { 443  @Nullable Map<K, V> delegate = delegateOrNull(); 444  return (delegate != null) ? delegate.containsKey(key) : indexOf(key) != -1; 445  } 446  447  @SuppressWarnings("unchecked") // known to be a V 448  @Override 449  public V get(@Nullable Object key) { 450  @Nullable Map<K, V> delegate = delegateOrNull(); 451  if (delegate != null) { 452  return delegate.get(key); 453  } 454  int index = indexOf(key); 455  if (index == -1) { 456  return null; 457  } 458  accessEntry(index); 459  return (V) values[index]; 460  } 461  462  @CanIgnoreReturnValue 463  @SuppressWarnings("unchecked") // known to be a V 464  @Override 465  public @Nullable V remove(@Nullable Object key) { 466  @Nullable Map<K, V> delegate = delegateOrNull(); 467  if (delegate != null) { 468  return delegate.remove(key); 469  } 470  Object oldValue = removeHelper(key); 471  return (oldValue == NOT_FOUND) ? null : (V) oldValue; 472  } 473  474  private @Nullable Object removeHelper(@Nullable Object key) { 475  if (needsAllocArrays()) { 476  return NOT_FOUND; 477  } 478  int mask = hashTableMask(); 479  int index = 480  CompactHashing.remove( 481  key, /* value= */ null, mask, table, entries, keys, /* values= */ null); 482  if (index == -1) { 483  return NOT_FOUND; 484  } 485  486  @Nullable Object oldValue = values[index]; 487  488  moveLastEntry(index, mask); 489  size--; 490  incrementModCount(); 491  492  return oldValue; 493  } 494  495  /** 496  * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. 497  */ 498  void moveLastEntry(int dstIndex, int mask) { 499  int srcIndex = size() - 1; 500  if (dstIndex < srcIndex) { 501  // move last entry to deleted spot 502  @Nullable Object key = keys[srcIndex]; 503  keys[dstIndex] = key; 504  values[dstIndex] = values[srcIndex]; 505  keys[srcIndex] = null; 506  values[srcIndex] = null; 507  508  // move the last entry to the removed spot, just like we moved the element 509  entries[dstIndex] = entries[srcIndex]; 510  entries[srcIndex] = 0; 511  512  // also need to update whoever's "next" pointer was pointing to the last entry place 513  int tableIndex = smearedHash(key) & mask; 514  int next = CompactHashing.tableGet(table, tableIndex); 515  int srcNext = srcIndex + 1; 516  if (next == srcNext) { 517  // we need to update the root pointer 518  CompactHashing.tableSet(table, tableIndex, dstIndex + 1); 519  } else { 520  // we need to update a pointer in an entry 521  int entryIndex; 522  int entry; 523  do { 524  entryIndex = next - 1; 525  entry = entries[entryIndex]; 526  next = CompactHashing.getNext(entry, mask); 527  } while (next != srcNext); 528  // here, entries[entryIndex] points to the old entry location; update it 529  entries[entryIndex] = CompactHashing.maskCombine(entry, dstIndex + 1, mask); 530  } 531  } else { 532  keys[dstIndex] = null; 533  values[dstIndex] = null; 534  entries[dstIndex] = 0; 535  } 536  } 537  538  int firstEntryIndex() { 539  return isEmpty() ? -1 : 0; 540  } 541  542  int getSuccessor(int entryIndex) { 543  return (entryIndex + 1 < size) ? entryIndex + 1 : -1; 544  } 545  546  /** 547  * Updates the index an iterator is pointing to after a call to remove: returns the index of the 548  * entry that should be looked at after a removal on indexRemoved, with indexBeforeRemove as the 549  * index that *was* the next entry that would be looked at. 550  */ 551  int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int indexRemoved) { 552  return indexBeforeRemove - 1; 553  } 554  555  private abstract class Itr<T> implements Iterator<T> { 556  int expectedMetadata = metadata; 557  int currentIndex = firstEntryIndex(); 558  int indexToRemove = -1; 559  560  @Override 561  public boolean hasNext() { 562  return currentIndex >= 0; 563  } 564  565  abstract T getOutput(int entry); 566  567  @Override 568  public T next() { 569  checkForConcurrentModification(); 570  if (!hasNext()) { 571  throw new NoSuchElementException(); 572  } 573  indexToRemove = currentIndex; 574  T result = getOutput(currentIndex); 575  currentIndex = getSuccessor(currentIndex); 576  return result; 577  } 578  579  @Override 580  public void remove() { 581  checkForConcurrentModification(); 582  checkRemove(indexToRemove >= 0); 583  incrementExpectedModCount(); 584  CompactHashMap.this.remove(keys[indexToRemove]); 585  currentIndex = adjustAfterRemove(currentIndex, indexToRemove); 586  indexToRemove = -1; 587  } 588  589  void incrementExpectedModCount() { 590  expectedMetadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; 591  } 592  593  private void checkForConcurrentModification() { 594  if (metadata != expectedMetadata) { 595  throw new ConcurrentModificationException(); 596  } 597  } 598  } 599  600  @SuppressWarnings("unchecked") // known to be Ks and Vs 601  @Override 602  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { 603  checkNotNull(function); 604  @Nullable Map<K, V> delegate = delegateOrNull(); 605  if (delegate != null) { 606  delegate.replaceAll(function); 607  } else { 608  for (int i = 0; i < size; i++) { 609  values[i] = function.apply((K) keys[i], (V) values[i]); 610  } 611  } 612  } 613  614  private transient @Nullable Set<K> keySetView; 615  616  @Override 617  public Set<K> keySet() { 618  return (keySetView == null) ? keySetView = createKeySet() : keySetView; 619  } 620  621  Set<K> createKeySet() { 622  return new KeySetView(); 623  } 624  625  @WeakOuter 626  class KeySetView extends Maps.KeySet<K, V> { 627  KeySetView() { 628  super(CompactHashMap.this); 629  } 630  631  @Override 632  public Object[] toArray() { 633  if (needsAllocArrays()) { 634  return new Object[0]; 635  } 636  @Nullable Map<K, V> delegate = delegateOrNull(); 637  return (delegate != null) 638  ? delegate.keySet().toArray() 639  : ObjectArrays.copyAsObjectArray(keys, 0, size); 640  } 641  642  @Override 643  public <T> T[] toArray(T[] a) { 644  if (needsAllocArrays()) { 645  if (a.length > 0) { 646  a[0] = null; 647  } 648  return a; 649  } 650  @Nullable Map<K, V> delegate = delegateOrNull(); 651  return (delegate != null) 652  ? delegate.keySet().toArray(a) 653  : ObjectArrays.toArrayImpl(keys, 0, size, a); 654  } 655  656  @Override 657  public boolean remove(@Nullable Object o) { 658  @Nullable Map<K, V> delegate = delegateOrNull(); 659  return (delegate != null) 660  ? delegate.keySet().remove(o) 661  : CompactHashMap.this.removeHelper(o) != NOT_FOUND; 662  } 663  664  @Override 665  public Iterator<K> iterator() { 666  return keySetIterator(); 667  } 668  669  @Override 670  public Spliterator<K> spliterator() { 671  if (needsAllocArrays()) { 672  return Spliterators.spliterator(new Object[0], Spliterator.DISTINCT | Spliterator.ORDERED); 673  } 674  @Nullable Map<K, V> delegate = delegateOrNull(); 675  return (delegate != null) 676  ? delegate.keySet().spliterator() 677  : Spliterators.spliterator(keys, 0, size, Spliterator.DISTINCT | Spliterator.ORDERED); 678  } 679  680  @SuppressWarnings("unchecked") // known to be Ks 681  @Override 682  public void forEach(Consumer<? super K> action) { 683  checkNotNull(action); 684  @Nullable Map<K, V> delegate = delegateOrNull(); 685  if (delegate != null) { 686  delegate.keySet().forEach(action); 687  } else { 688  for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { 689  action.accept((K) keys[i]); 690  } 691  } 692  } 693  } 694  695  Iterator<K> keySetIterator() { 696  @Nullable Map<K, V> delegate = delegateOrNull(); 697  if (delegate != null) { 698  return delegate.keySet().iterator(); 699  } 700  return new Itr<K>() { 701  @SuppressWarnings("unchecked") // known to be a K 702  @Override 703  K getOutput(int entry) { 704  return (K) keys[entry]; 705  } 706  }; 707  } 708  709  @SuppressWarnings("unchecked") // known to be Ks and Vs 710  @Override 711  public void forEach(BiConsumer<? super K, ? super V> action) { 712  checkNotNull(action); 713  @Nullable Map<K, V> delegate = delegateOrNull(); 714  if (delegate != null) { 715  delegate.forEach(action); 716  } else { 717  for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { 718  action.accept((K) keys[i], (V) values[i]); 719  } 720  } 721  } 722  723  private transient @Nullable Set<Entry<K, V>> entrySetView; 724  725  @Override 726  public Set<Entry<K, V>> entrySet() { 727  return (entrySetView == null) ? entrySetView = createEntrySet() : entrySetView; 728  } 729  730  Set<Entry<K, V>> createEntrySet() { 731  return new EntrySetView(); 732  } 733  734  @WeakOuter 735  class EntrySetView extends Maps.EntrySet<K, V> { 736  @Override 737  Map<K, V> map() { 738  return CompactHashMap.this; 739  } 740  741  @Override 742  public Iterator<Entry<K, V>> iterator() { 743  return entrySetIterator(); 744  } 745  746  @Override 747  public Spliterator<Entry<K, V>> spliterator() { 748  @Nullable Map<K, V> delegate = delegateOrNull(); 749  return (delegate != null) 750  ? delegate.entrySet().spliterator() 751  : CollectSpliterators.indexed( 752  size, Spliterator.DISTINCT | Spliterator.ORDERED, MapEntry::new); 753  } 754  755  @Override 756  public boolean contains(@Nullable Object o) { 757  @Nullable Map<K, V> delegate = delegateOrNull(); 758  if (delegate != null) { 759  return delegate.entrySet().contains(o); 760  } else if (o instanceof Entry) { 761  Entry<?, ?> entry = (Entry<?, ?>) o; 762  int index = indexOf(entry.getKey()); 763  return index != -1 && Objects.equal(values[index], entry.getValue()); 764  } 765  return false; 766  } 767  768  @Override 769  public boolean remove(@Nullable Object o) { 770  @Nullable Map<K, V> delegate = delegateOrNull(); 771  if (delegate != null) { 772  return delegate.entrySet().remove(o); 773  } else if (o instanceof Entry) { 774  Entry<?, ?> entry = (Entry<?, ?>) o; 775  if (needsAllocArrays()) { 776  return false; 777  } 778  int mask = hashTableMask(); 779  int index = 780  CompactHashing.remove( 781  entry.getKey(), entry.getValue(), mask, table, entries, keys, values); 782  if (index == -1) { 783  return false; 784  } 785  786  moveLastEntry(index, mask); 787  size--; 788  incrementModCount(); 789  790  return true; 791  } 792  return false; 793  } 794  } 795  796  Iterator<Entry<K, V>> entrySetIterator() { 797  @Nullable Map<K, V> delegate = delegateOrNull(); 798  if (delegate != null) { 799  return delegate.entrySet().iterator(); 800  } 801  return new Itr<Entry<K, V>>() { 802  @Override 803  Entry<K, V> getOutput(int entry) { 804  return new MapEntry(entry); 805  } 806  }; 807  } 808  809  final class MapEntry extends AbstractMapEntry<K, V> { 810  private final @Nullable K key; 811  812  private int lastKnownIndex; 813  814  @SuppressWarnings("unchecked") // known to be a K 815  MapEntry(int index) { 816  this.key = (K) keys[index]; 817  this.lastKnownIndex = index; 818  } 819  820  @Nullable 821  @Override 822  public K getKey() { 823  return key; 824  } 825  826  private void updateLastKnownIndex() { 827  if (lastKnownIndex == -1 828  || lastKnownIndex >= size() 829  || !Objects.equal(key, keys[lastKnownIndex])) { 830  lastKnownIndex = indexOf(key); 831  } 832  } 833  834  @SuppressWarnings("unchecked") // known to be a V 835  @Override 836  public @Nullable V getValue() { 837  @Nullable Map<K, V> delegate = delegateOrNull(); 838  if (delegate != null) { 839  return delegate.get(key); 840  } 841  updateLastKnownIndex(); 842  return (lastKnownIndex == -1) ? null : (V) values[lastKnownIndex]; 843  } 844  845  @SuppressWarnings("unchecked") // known to be a V 846  @Override 847  public V setValue(V value) { 848  @Nullable Map<K, V> delegate = delegateOrNull(); 849  if (delegate != null) { 850  return delegate.put(key, value); 851  } 852  updateLastKnownIndex(); 853  if (lastKnownIndex == -1) { 854  put(key, value); 855  return null; 856  } else { 857  V old = (V) values[lastKnownIndex]; 858  values[lastKnownIndex] = value; 859  return old; 860  } 861  } 862  } 863  864  @Override 865  public int size() { 866  @Nullable Map<K, V> delegate = delegateOrNull(); 867  return (delegate != null) ? delegate.size() : size; 868  } 869  870  @Override 871  public boolean isEmpty() { 872  return size() == 0; 873  } 874  875  @Override 876  public boolean containsValue(@Nullable Object value) { 877  @Nullable Map<K, V> delegate = delegateOrNull(); 878  if (delegate != null) { 879  return delegate.containsValue(value); 880  } 881  for (int i = 0; i < size; i++) { 882  if (Objects.equal(value, values[i])) { 883  return true; 884  } 885  } 886  return false; 887  } 888  889  private transient @Nullable Collection<V> valuesView; 890  891  @Override 892  public Collection<V> values() { 893  return (valuesView == null) ? valuesView = createValues() : valuesView; 894  } 895  896  Collection<V> createValues() { 897  return new ValuesView(); 898  } 899  900  @WeakOuter 901  class ValuesView extends Maps.Values<K, V> { 902  ValuesView() { 903  super(CompactHashMap.this); 904  } 905  906  @Override 907  public Iterator<V> iterator() { 908  return valuesIterator(); 909  } 910  911  @SuppressWarnings("unchecked") // known to be Vs 912  @Override 913  public void forEach(Consumer<? super V> action) { 914  checkNotNull(action); 915  @Nullable Map<K, V> delegate = delegateOrNull(); 916  if (delegate != null) { 917  delegate.values().forEach(action); 918  } else { 919  for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { 920  action.accept((V) values[i]); 921  } 922  } 923  } 924  925  @Override 926  public Spliterator<V> spliterator() { 927  if (needsAllocArrays()) { 928  return Spliterators.spliterator(new Object[0], Spliterator.ORDERED); 929  } 930  @Nullable Map<K, V> delegate = delegateOrNull(); 931  return (delegate != null) 932  ? delegate.values().spliterator() 933  : Spliterators.spliterator(values, 0, size, Spliterator.ORDERED); 934  } 935  936  @Override 937  public Object[] toArray() { 938  if (needsAllocArrays()) { 939  return new Object[0]; 940  } 941  @Nullable Map<K, V> delegate = delegateOrNull(); 942  return (delegate != null) 943  ? delegate.values().toArray() 944  : ObjectArrays.copyAsObjectArray(values, 0, size); 945  } 946  947  @Override 948  public <T> T[] toArray(T[] a) { 949  if (needsAllocArrays()) { 950  if (a.length > 0) { 951  a[0] = null; 952  } 953  return a; 954  } 955  @Nullable Map<K, V> delegate = delegateOrNull(); 956  return (delegate != null) 957  ? delegate.values().toArray(a) 958  : ObjectArrays.toArrayImpl(values, 0, size, a); 959  } 960  } 961  962  Iterator<V> valuesIterator() { 963  @Nullable Map<K, V> delegate = delegateOrNull(); 964  if (delegate != null) { 965  return delegate.values().iterator(); 966  } 967  return new Itr<V>() { 968  @SuppressWarnings("unchecked") // known to be a V 969  @Override 970  V getOutput(int entry) { 971  return (V) values[entry]; 972  } 973  }; 974  } 975  976  /** 977  * Ensures that this {@code CompactHashMap} has the smallest representation in memory, given its 978  * current size. 979  */ 980  public void trimToSize() { 981  if (needsAllocArrays()) { 982  return; 983  } 984  @Nullable Map<K, V> delegate = delegateOrNull(); 985  if (delegate != null) { 986  Map<K, V> newDelegate = createHashFloodingResistantDelegate(size()); 987  newDelegate.putAll(delegate); 988  this.table = newDelegate; 989  return; 990  } 991  int size = this.size; 992  if (size < entries.length) { 993  resizeEntries(size); 994  } 995  int minimumTableSize = CompactHashing.tableSize(size); 996  int mask = hashTableMask(); 997  if (minimumTableSize < mask) { // smaller table size will always be less than current mask 998  resizeTable(mask, minimumTableSize, UNSET, UNSET); 999  } 1000  } 1001  1002  @Override 1003  public void clear() { 1004  if (needsAllocArrays()) { 1005  return; 1006  } 1007  incrementModCount(); 1008  @Nullable Map<K, V> delegate = delegateOrNull(); 1009  if (delegate != null) { 1010  metadata = 1011  Ints.constrainToRange(size(), CompactHashing.DEFAULT_SIZE, CompactHashing.MAX_SIZE); 1012  delegate.clear(); // invalidate any iterators left over! 1013  table = null; 1014  size = 0; 1015  } else { 1016  Arrays.fill(keys, 0, size, null); 1017  Arrays.fill(values, 0, size, null); 1018  CompactHashing.tableClear(table); 1019  Arrays.fill(entries, 0, size, 0); 1020  this.size = 0; 1021  } 1022  } 1023  1024  private void writeObject(ObjectOutputStream stream) throws IOException { 1025  stream.defaultWriteObject(); 1026  stream.writeInt(size()); 1027  Iterator<Entry<K, V>> entryIterator = entrySetIterator(); 1028  while (entryIterator.hasNext()) { 1029  Entry<K, V> e = entryIterator.next(); 1030  stream.writeObject(e.getKey()); 1031  stream.writeObject(e.getValue()); 1032  } 1033  } 1034  1035  @SuppressWarnings("unchecked") 1036  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 1037  stream.defaultReadObject(); 1038  int elementCount = stream.readInt(); 1039  if (elementCount < 0) { 1040  throw new InvalidObjectException("Invalid size: " + elementCount); 1041  } 1042  init(elementCount); 1043  for (int i = 0; i < elementCount; i++) { 1044  K key = (K) stream.readObject(); 1045  V value = (V) stream.readObject(); 1046  put(key, value); 1047  } 1048  } 1049 }