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