001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.collect;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.base.Preconditions.checkNotNull;
019import static com.google.common.collect.CollectPreconditions.checkNonnegative;
020import static com.google.common.collect.CollectPreconditions.checkRemove;
021import static com.google.common.collect.Hashing.smearedHash;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.annotations.GwtIncompatible;
025import com.google.common.base.Objects;
026import com.google.common.collect.Maps.IteratorBasedAbstractMap;
027import com.google.errorprone.annotations.CanIgnoreReturnValue;
028import com.google.errorprone.annotations.concurrent.LazyInit;
029import com.google.j2objc.annotations.RetainedWith;
030import com.google.j2objc.annotations.WeakOuter;
031import java.io.IOException;
032import java.io.ObjectInputStream;
033import java.io.ObjectOutputStream;
034import java.io.Serializable;
035import java.util.Arrays;
036import java.util.ConcurrentModificationException;
037import java.util.Iterator;
038import java.util.Map;
039import java.util.NoSuchElementException;
040import java.util.Set;
041import java.util.function.BiConsumer;
042import java.util.function.BiFunction;
043import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
044import org.checkerframework.checker.nullness.qual.Nullable;
045
046/**
047 * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A
048 * {@code HashBiMap} and its inverse are both serializable.
049 *
050 * <p>This implementation guarantees insertion-based iteration order of its keys.
051 *
052 * <p>See the Guava User Guide article on <a href=
053 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap"> {@code BiMap} </a>.
054 *
055 * @author Louis Wasserman
056 * @author Mike Bostock
057 * @since 2.0
058 */
059@GwtCompatible(emulated = true)
060public final class HashBiMap<K, V> extends IteratorBasedAbstractMap<K, V>
061    implements BiMap<K, V>, Serializable {
062
063  /** Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */
064  public static <K, V> HashBiMap<K, V> create() {
065    return create(16);
066  }
067
068  /**
069   * Constructs a new, empty bimap with the specified expected size.
070   *
071   * @param expectedSize the expected number of entries
072   * @throws IllegalArgumentException if the specified expected size is negative
073   */
074  public static <K, V> HashBiMap<K, V> create(int expectedSize) {
075    return new HashBiMap<>(expectedSize);
076  }
077
078  /**
079   * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an
080   * initial capacity sufficient to hold the mappings in the specified map.
081   */
082  public static <K, V> HashBiMap<K, V> create(Map<? extends K, ? extends V> map) {
083    HashBiMap<K, V> bimap = create(map.size());
084    bimap.putAll(map);
085    return bimap;
086  }
087
088  private static final class BiEntry<K, V> extends ImmutableEntry<K, V> {
089    final int keyHash;
090    final int valueHash;
091
092    @Nullable BiEntry<K, V> nextInKToVBucket;
093    @Nullable BiEntry<K, V> nextInVToKBucket;
094
095    @Nullable BiEntry<K, V> nextInKeyInsertionOrder;
096    @Nullable BiEntry<K, V> prevInKeyInsertionOrder;
097
098    BiEntry(K key, int keyHash, V value, int valueHash) {
099      super(key, value);
100      this.keyHash = keyHash;
101      this.valueHash = valueHash;
102    }
103  }
104
105  private static final double LOAD_FACTOR = 1.0;
106
107  private transient BiEntry<K, V>[] hashTableKToV;
108  private transient BiEntry<K, V>[] hashTableVToK;
109  private transient @Nullable BiEntry<K, V> firstInKeyInsertionOrder;
110  private transient @Nullable BiEntry<K, V> lastInKeyInsertionOrder;
111  private transient int size;
112  private transient int mask;
113  private transient int modCount;
114
115  private HashBiMap(int expectedSize) {
116    init(expectedSize);
117  }
118
119  private void init(int expectedSize) {
120    checkNonnegative(expectedSize, "expectedSize");
121    int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR);
122    this.hashTableKToV = createTable(tableSize);
123    this.hashTableVToK = createTable(tableSize);
124    this.firstInKeyInsertionOrder = null;
125    this.lastInKeyInsertionOrder = null;
126    this.size = 0;
127    this.mask = tableSize - 1;
128    this.modCount = 0;
129  }
130
131  /**
132   * Finds and removes {@code entry} from the bucket linked lists in both the key-to-value direction
133   * and the value-to-key direction.
134   */
135  private void delete(BiEntry<K, V> entry) {
136    int keyBucket = entry.keyHash & mask;
137    BiEntry<K, V> prevBucketEntry = null;
138    for (BiEntry<K, V> bucketEntry = hashTableKToV[keyBucket];
139        true;
140        bucketEntry = bucketEntry.nextInKToVBucket) {
141      if (bucketEntry == entry) {
142        if (prevBucketEntry == null) {
143          hashTableKToV[keyBucket] = entry.nextInKToVBucket;
144        } else {
145          prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket;
146        }
147        break;
148      }
149      prevBucketEntry = bucketEntry;
150    }
151
152    int valueBucket = entry.valueHash & mask;
153    prevBucketEntry = null;
154    for (BiEntry<K, V> bucketEntry = hashTableVToK[valueBucket];
155        true;
156        bucketEntry = bucketEntry.nextInVToKBucket) {
157      if (bucketEntry == entry) {
158        if (prevBucketEntry == null) {
159          hashTableVToK[valueBucket] = entry.nextInVToKBucket;
160        } else {
161          prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket;
162        }
163        break;
164      }
165      prevBucketEntry = bucketEntry;
166    }
167
168    if (entry.prevInKeyInsertionOrder == null) {
169      firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
170    } else {
171      entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
172    }
173
174    if (entry.nextInKeyInsertionOrder == null) {
175      lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
176    } else {
177      entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
178    }
179
180    size--;
181    modCount++;
182  }
183
184  private void insert(BiEntry<K, V> entry, @Nullable BiEntry<K, V> oldEntryForKey) {
185    int keyBucket = entry.keyHash & mask;
186    entry.nextInKToVBucket = hashTableKToV[keyBucket];
187    hashTableKToV[keyBucket] = entry;
188
189    int valueBucket = entry.valueHash & mask;
190    entry.nextInVToKBucket = hashTableVToK[valueBucket];
191    hashTableVToK[valueBucket] = entry;
192
193    if (oldEntryForKey == null) {
194      entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
195      entry.nextInKeyInsertionOrder = null;
196      if (lastInKeyInsertionOrder == null) {
197        firstInKeyInsertionOrder = entry;
198      } else {
199        lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
200      }
201      lastInKeyInsertionOrder = entry;
202    } else {
203      entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder;
204      if (entry.prevInKeyInsertionOrder == null) {
205        firstInKeyInsertionOrder = entry;
206      } else {
207        entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
208      }
209      entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder;
210      if (entry.nextInKeyInsertionOrder == null) {
211        lastInKeyInsertionOrder = entry;
212      } else {
213        entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry;
214      }
215    }
216
217    size++;
218    modCount++;
219  }
220
221  private BiEntry<K, V> seekByKey(@Nullable Object key, int keyHash) {
222    for (BiEntry<K, V> entry = hashTableKToV[keyHash & mask];
223        entry != null;
224        entry = entry.nextInKToVBucket) {
225      if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) {
226        return entry;
227      }
228    }
229    return null;
230  }
231
232  private BiEntry<K, V> seekByValue(@Nullable Object value, int valueHash) {
233    for (BiEntry<K, V> entry = hashTableVToK[valueHash & mask];
234        entry != null;
235        entry = entry.nextInVToKBucket) {
236      if (valueHash == entry.valueHash && Objects.equal(value, entry.value)) {
237        return entry;
238      }
239    }
240    return null;
241  }
242
243  @Override
244  public boolean containsKey(@Nullable Object key) {
245    return seekByKey(key, smearedHash(key)) != null;
246  }
247
248  /**
249   * Returns {@code true} if this BiMap contains an entry whose value is equal to {@code value} (or,
250   * equivalently, if this inverse view contains a key that is equal to {@code value}).
251   *
252   * <p>Due to the property that values in a BiMap are unique, this will tend to execute in
253   * faster-than-linear time.
254   *
255   * @param value the object to search for in the values of this BiMap
256   * @return true if a mapping exists from a key to the specified value
257   */
258  @Override
259  public boolean containsValue(@Nullable Object value) {
260    return seekByValue(value, smearedHash(value)) != null;
261  }
262
263  @Override
264  public @Nullable V get(@Nullable Object key) {
265    return Maps.valueOrNull(seekByKey(key, smearedHash(key)));
266  }
267
268  @CanIgnoreReturnValue
269  @Override
270  public V put(@Nullable K key, @Nullable V value) {
271    return put(key, value, false);
272  }
273
274  private V put(@Nullable K key, @Nullable V value, boolean force) {
275    int keyHash = smearedHash(key);
276    int valueHash = smearedHash(value);
277
278    BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);
279    if (oldEntryForKey != null
280        && valueHash == oldEntryForKey.valueHash
281        && Objects.equal(value, oldEntryForKey.value)) {
282      return value;
283    }
284
285    BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);
286    if (oldEntryForValue != null) {
287      if (force) {
288        delete(oldEntryForValue);
289      } else {
290        throw new IllegalArgumentException("value already present: " + value);
291      }
292    }
293
294    BiEntry<K, V> newEntry = new BiEntry<>(key, keyHash, value, valueHash);
295    if (oldEntryForKey != null) {
296      delete(oldEntryForKey);
297      insert(newEntry, oldEntryForKey);
298      oldEntryForKey.prevInKeyInsertionOrder = null;
299      oldEntryForKey.nextInKeyInsertionOrder = null;
300      return oldEntryForKey.value;
301    } else {
302      insert(newEntry, null);
303      rehashIfNecessary();
304      return null;
305    }
306  }
307
308  @CanIgnoreReturnValue
309  @Override
310  @Nullable
311  public V forcePut(@Nullable K key, @Nullable V value) {
312    return put(key, value, true);
313  }
314
315  private @Nullable K putInverse(@Nullable V value, @Nullable K key, boolean force) {
316    int valueHash = smearedHash(value);
317    int keyHash = smearedHash(key);
318
319    BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);
320    BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);
321    if (oldEntryForValue != null
322        && keyHash == oldEntryForValue.keyHash
323        && Objects.equal(key, oldEntryForValue.key)) {
324      return key;
325    } else if (oldEntryForKey != null && !force) {
326      throw new IllegalArgumentException("key already present: " + key);
327    }
328
329    /*
330     * The ordering here is important: if we deleted the key entry and then the value entry,
331     * the key entry's prev or next pointer might point to the dead value entry, and when we
332     * put the new entry in the key entry's position in iteration order, it might invalidate
333     * the linked list.
334     */
335
336    if (oldEntryForValue != null) {
337      delete(oldEntryForValue);
338    }
339
340    if (oldEntryForKey != null) {
341      delete(oldEntryForKey);
342    }
343
344    BiEntry<K, V> newEntry = new BiEntry<>(key, keyHash, value, valueHash);
345    insert(newEntry, oldEntryForKey);
346
347    if (oldEntryForKey != null) {
348      oldEntryForKey.prevInKeyInsertionOrder = null;
349      oldEntryForKey.nextInKeyInsertionOrder = null;
350    }
351    if (oldEntryForValue != null) {
352      oldEntryForValue.prevInKeyInsertionOrder = null;
353      oldEntryForValue.nextInKeyInsertionOrder = null;
354    }
355    rehashIfNecessary();
356    return Maps.keyOrNull(oldEntryForValue);
357  }
358
359  private void rehashIfNecessary() {
360    BiEntry<K, V>[] oldKToV = hashTableKToV;
361    if (Hashing.needsResizing(size, oldKToV.length, LOAD_FACTOR)) {
362      int newTableSize = oldKToV.length * 2;
363
364      this.hashTableKToV = createTable(newTableSize);
365      this.hashTableVToK = createTable(newTableSize);
366      this.mask = newTableSize - 1;
367      this.size = 0;
368
369      for (BiEntry<K, V> entry = firstInKeyInsertionOrder;
370          entry != null;
371          entry = entry.nextInKeyInsertionOrder) {
372        insert(entry, entry);
373      }
374      this.modCount++;
375    }
376  }
377
378  @SuppressWarnings("unchecked")
379  private BiEntry<K, V>[] createTable(int length) {
380    return new BiEntry[length];
381  }
382
383  @CanIgnoreReturnValue
384  @Override
385  @Nullable
386  public V remove(@Nullable Object key) {
387    BiEntry<K, V> entry = seekByKey(key, smearedHash(key));
388    if (entry == null) {
389      return null;
390    } else {
391      delete(entry);
392      entry.prevInKeyInsertionOrder = null;
393      entry.nextInKeyInsertionOrder = null;
394      return entry.value;
395    }
396  }
397
398  @Override
399  public void clear() {
400    size = 0;
401    Arrays.fill(hashTableKToV, null);
402    Arrays.fill(hashTableVToK, null);
403    firstInKeyInsertionOrder = null;
404    lastInKeyInsertionOrder = null;
405    modCount++;
406  }
407
408  @Override
409  public int size() {
410    return size;
411  }
412
413  abstract class Itr<T> implements Iterator<T> {
414    BiEntry<K, V> next = firstInKeyInsertionOrder;
415    BiEntry<K, V> toRemove = null;
416    int expectedModCount = modCount;
417    int remaining = size();
418
419    @Override
420    public boolean hasNext() {
421      if (modCount != expectedModCount) {
422        throw new ConcurrentModificationException();
423      }
424      return next != null && remaining > 0;
425    }
426
427    @Override
428    public T next() {
429      if (!hasNext()) {
430        throw new NoSuchElementException();
431      }
432
433      BiEntry<K, V> entry = next;
434      next = entry.nextInKeyInsertionOrder;
435      toRemove = entry;
436      remaining--;
437      return output(entry);
438    }
439
440    @Override
441    public void remove() {
442      if (modCount != expectedModCount) {
443        throw new ConcurrentModificationException();
444      }
445      checkRemove(toRemove != null);
446      delete(toRemove);
447      expectedModCount = modCount;
448      toRemove = null;
449    }
450
451    abstract T output(BiEntry<K, V> entry);
452  }
453
454  @Override
455  public Set<K> keySet() {
456    return new KeySet();
457  }
458
459  @WeakOuter
460  private final class KeySet extends Maps.KeySet<K, V> {
461    KeySet() {
462      super(HashBiMap.this);
463    }
464
465    @Override
466    public Iterator<K> iterator() {
467      return new Itr<K>() {
468        @Override
469        K output(BiEntry<K, V> entry) {
470          return entry.key;
471        }
472      };
473    }
474
475    @Override
476    public boolean remove(@Nullable Object o) {
477      BiEntry<K, V> entry = seekByKey(o, smearedHash(o));
478      if (entry == null) {
479        return false;
480      } else {
481        delete(entry);
482        entry.prevInKeyInsertionOrder = null;
483        entry.nextInKeyInsertionOrder = null;
484        return true;
485      }
486    }
487  }
488
489  @Override
490  public Set<V> values() {
491    return inverse().keySet();
492  }
493
494  @Override
495  Iterator<Entry<K, V>> entryIterator() {
496    return new Itr<Entry<K, V>>() {
497      @Override
498      Entry<K, V> output(BiEntry<K, V> entry) {
499        return new MapEntry(entry);
500      }
501
502      class MapEntry extends AbstractMapEntry<K, V> {
503        BiEntry<K, V> delegate;
504
505        MapEntry(BiEntry<K, V> entry) {
506          this.delegate = entry;
507        }
508
509        @Override
510        public K getKey() {
511          return delegate.key;
512        }
513
514        @Override
515        public V getValue() {
516          return delegate.value;
517        }
518
519        @Override
520        public V setValue(V value) {
521          V oldValue = delegate.value;
522          int valueHash = smearedHash(value);
523          if (valueHash == delegate.valueHash && Objects.equal(value, oldValue)) {
524            return value;
525          }
526          checkArgument(seekByValue(value, valueHash) == null, "value already present: %s", value);
527          delete(delegate);
528          BiEntry<K, V> newEntry = new BiEntry<>(delegate.key, delegate.keyHash, value, valueHash);
529          insert(newEntry, delegate);
530          delegate.prevInKeyInsertionOrder = null;
531          delegate.nextInKeyInsertionOrder = null;
532          expectedModCount = modCount;
533          if (toRemove == delegate) {
534            toRemove = newEntry;
535          }
536          delegate = newEntry;
537          return oldValue;
538        }
539      }
540    };
541  }
542
543  @Override
544  public void forEach(BiConsumer<? super K, ? super V> action) {
545    checkNotNull(action);
546    for (BiEntry<K, V> entry = firstInKeyInsertionOrder;
547        entry != null;
548        entry = entry.nextInKeyInsertionOrder) {
549      action.accept(entry.key, entry.value);
550    }
551  }
552
553  @Override
554  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
555    checkNotNull(function);
556    BiEntry<K, V> oldFirst = firstInKeyInsertionOrder;
557    clear();
558    for (BiEntry<K, V> entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) {
559      put(entry.key, function.apply(entry.key, entry.value));
560    }
561  }
562
563  @LazyInit @MonotonicNonNull @RetainedWith private transient BiMap<V, K> inverse;
564
565  @Override
566  public BiMap<V, K> inverse() {
567    BiMap<V, K> result = inverse;
568    return (result == null) ? inverse = new Inverse() : result;
569  }
570
571  private final class Inverse extends IteratorBasedAbstractMap<V, K>
572      implements BiMap<V, K>, Serializable {
573    BiMap<K, V> forward() {
574      return HashBiMap.this;
575    }
576
577    @Override
578    public int size() {
579      return size;
580    }
581
582    @Override
583    public void clear() {
584      forward().clear();
585    }
586
587    @Override
588    public boolean containsKey(@Nullable Object value) {
589      return forward().containsValue(value);
590    }
591
592    @Override
593    public K get(@Nullable Object value) {
594      return Maps.keyOrNull(seekByValue(value, smearedHash(value)));
595    }
596
597    @CanIgnoreReturnValue
598    @Override
599    @Nullable
600    public K put(@Nullable V value, @Nullable K key) {
601      return putInverse(value, key, false);
602    }
603
604    @Override
605    @Nullable
606    public K forcePut(@Nullable V value, @Nullable K key) {
607      return putInverse(value, key, true);
608    }
609
610    @Override
611    @Nullable
612    public K remove(@Nullable Object value) {
613      BiEntry<K, V> entry = seekByValue(value, smearedHash(value));
614      if (entry == null) {
615        return null;
616      } else {
617        delete(entry);
618        entry.prevInKeyInsertionOrder = null;
619        entry.nextInKeyInsertionOrder = null;
620        return entry.key;
621      }
622    }
623
624    @Override
625    public BiMap<K, V> inverse() {
626      return forward();
627    }
628
629    @Override
630    public Set<V> keySet() {
631      return new InverseKeySet();
632    }
633
634    @WeakOuter
635    private final class InverseKeySet extends Maps.KeySet<V, K> {
636      InverseKeySet() {
637        super(Inverse.this);
638      }
639
640      @Override
641      public boolean remove(@Nullable Object o) {
642        BiEntry<K, V> entry = seekByValue(o, smearedHash(o));
643        if (entry == null) {
644          return false;
645        } else {
646          delete(entry);
647          return true;
648        }
649      }
650
651      @Override
652      public Iterator<V> iterator() {
653        return new Itr<V>() {
654          @Override
655          V output(BiEntry<K, V> entry) {
656            return entry.value;
657          }
658        };
659      }
660    }
661
662    @Override
663    public Set<K> values() {
664      return forward().keySet();
665    }
666
667    @Override
668    Iterator<Entry<V, K>> entryIterator() {
669      return new Itr<Entry<V, K>>() {
670        @Override
671        Entry<V, K> output(BiEntry<K, V> entry) {
672          return new InverseEntry(entry);
673        }
674
675        class InverseEntry extends AbstractMapEntry<V, K> {
676          BiEntry<K, V> delegate;
677
678          InverseEntry(BiEntry<K, V> entry) {
679            this.delegate = entry;
680          }
681
682          @Override
683          public V getKey() {
684            return delegate.value;
685          }
686
687          @Override
688          public K getValue() {
689            return delegate.key;
690          }
691
692          @Override
693          public K setValue(K key) {
694            K oldKey = delegate.key;
695            int keyHash = smearedHash(key);
696            if (keyHash == delegate.keyHash && Objects.equal(key, oldKey)) {
697              return key;
698            }
699            checkArgument(seekByKey(key, keyHash) == null, "value already present: %s", key);
700            delete(delegate);
701            BiEntry<K, V> newEntry =
702                new BiEntry<>(key, keyHash, delegate.value, delegate.valueHash);
703            delegate = newEntry;
704            insert(newEntry, null);
705            expectedModCount = modCount;
706            return oldKey;
707          }
708        }
709      };
710    }
711
712    @Override
713    public void forEach(BiConsumer<? super V, ? super K> action) {
714      checkNotNull(action);
715      HashBiMap.this.forEach((k, v) -> action.accept(v, k));
716    }
717
718    @Override
719    public void replaceAll(BiFunction<? super V, ? super K, ? extends K> function) {
720      checkNotNull(function);
721      BiEntry<K, V> oldFirst = firstInKeyInsertionOrder;
722      clear();
723      for (BiEntry<K, V> entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) {
724        put(entry.value, function.apply(entry.value, entry.key));
725      }
726    }
727
728    Object writeReplace() {
729      return new InverseSerializedForm<>(HashBiMap.this);
730    }
731  }
732
733  private static final class InverseSerializedForm<K, V> implements Serializable {
734    private final HashBiMap<K, V> bimap;
735
736    InverseSerializedForm(HashBiMap<K, V> bimap) {
737      this.bimap = bimap;
738    }
739
740    Object readResolve() {
741      return bimap.inverse();
742    }
743  }
744
745  /**
746   * @serialData the number of entries, first key, first value, second key, second value, and so on.
747   */
748  @GwtIncompatible // java.io.ObjectOutputStream
749  private void writeObject(ObjectOutputStream stream) throws IOException {
750    stream.defaultWriteObject();
751    Serialization.writeMap(this, stream);
752  }
753
754  @GwtIncompatible // java.io.ObjectInputStream
755  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
756    stream.defaultReadObject();
757    int size = Serialization.readCount(stream);
758    init(16); // resist hostile attempts to allocate gratuitous heap
759    Serialization.populateMap(this, stream, size);
760  }
761
762  @GwtIncompatible // Not needed in emulated source
763  private static final long serialVersionUID = 0;
764}