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