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}