001/* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.collect; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020import static com.google.common.base.Preconditions.checkState; 021import static com.google.common.collect.CollectPreconditions.checkNonnegative; 022import static java.util.Objects.requireNonNull; 023 024import com.google.common.annotations.GwtCompatible; 025import com.google.common.annotations.GwtIncompatible; 026import com.google.common.annotations.J2ktIncompatible; 027import com.google.common.annotations.VisibleForTesting; 028import com.google.common.base.Objects; 029import com.google.errorprone.annotations.CanIgnoreReturnValue; 030import com.google.j2objc.annotations.WeakOuter; 031import java.io.IOException; 032import java.io.ObjectInputStream; 033import java.io.ObjectOutputStream; 034import java.util.Arrays; 035import java.util.Collection; 036import java.util.ConcurrentModificationException; 037import java.util.Iterator; 038import java.util.Map; 039import java.util.Map.Entry; 040import java.util.NoSuchElementException; 041import java.util.Set; 042import java.util.Spliterator; 043import java.util.Spliterators; 044import java.util.function.Consumer; 045import org.jspecify.annotations.Nullable; 046 047/** 048 * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that 049 * returns collections whose iterators follow the ordering in which the data was added to the 050 * multimap. 051 * 052 * <p>The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through 053 * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code 054 * removeAll}, and {@code replaceValues} return collections that iterate through the values in the 055 * order they were added. The collections generated by {@code entries} and {@code values} iterate 056 * across the key-value mappings in the order they were added to the multimap. 057 * 058 * <p>The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and 059 * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or 060 * removing mappings does not affect the key iteration order. However, if you remove all values 061 * associated with a key and then add the key back to the multimap, that key will come last in the 062 * key iteration order. 063 * 064 * <p>The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an 065 * existing key-value pair has no effect. 066 * 067 * <p>Keys and values may be null. All optional multimap methods are supported, and all returned 068 * views are modifiable. 069 * 070 * <p>This class is not threadsafe when any concurrent operations update the multimap. Concurrent 071 * read operations will work correctly. To allow concurrent update operations, wrap your multimap 072 * with a call to {@link Multimaps#synchronizedSetMultimap}. 073 * 074 * <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code LinkedHashMultimap} 075 * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will 076 * result. 077 * 078 * <p>See the Guava User Guide article on <a href= 079 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}</a>. 080 * 081 * @author Jared Levy 082 * @author Louis Wasserman 083 * @since 2.0 084 */ 085@GwtCompatible(serializable = true, emulated = true) 086public final class LinkedHashMultimap<K extends @Nullable Object, V extends @Nullable Object> 087 extends LinkedHashMultimapGwtSerializationDependencies<K, V> { 088 089 /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */ 090 public static <K extends @Nullable Object, V extends @Nullable Object> 091 LinkedHashMultimap<K, V> create() { 092 return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); 093 } 094 095 /** 096 * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified 097 * numbers of keys and values without rehashing. 098 * 099 * @param expectedKeys the expected number of distinct keys 100 * @param expectedValuesPerKey the expected average number of values per key 101 * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is 102 * negative 103 */ 104 public static <K extends @Nullable Object, V extends @Nullable Object> 105 LinkedHashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) { 106 return new LinkedHashMultimap<>( 107 Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey)); 108 } 109 110 /** 111 * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a 112 * key-value mapping appears multiple times in the input multimap, it only appears once in the 113 * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order 114 * as the input multimap, except for excluding duplicate mappings. 115 * 116 * @param multimap the multimap whose contents are copied to this multimap 117 */ 118 public static <K extends @Nullable Object, V extends @Nullable Object> 119 LinkedHashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) { 120 LinkedHashMultimap<K, V> result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); 121 result.putAll(multimap); 122 return result; 123 } 124 125 private interface ValueSetLink<K extends @Nullable Object, V extends @Nullable Object> { 126 ValueSetLink<K, V> getPredecessorInValueSet(); 127 128 ValueSetLink<K, V> getSuccessorInValueSet(); 129 130 void setPredecessorInValueSet(ValueSetLink<K, V> entry); 131 132 void setSuccessorInValueSet(ValueSetLink<K, V> entry); 133 } 134 135 private static <K extends @Nullable Object, V extends @Nullable Object> void succeedsInValueSet( 136 ValueSetLink<K, V> pred, ValueSetLink<K, V> succ) { 137 pred.setSuccessorInValueSet(succ); 138 succ.setPredecessorInValueSet(pred); 139 } 140 141 private static <K extends @Nullable Object, V extends @Nullable Object> void succeedsInMultimap( 142 ValueEntry<K, V> pred, ValueEntry<K, V> succ) { 143 pred.setSuccessorInMultimap(succ); 144 succ.setPredecessorInMultimap(pred); 145 } 146 147 private static <K extends @Nullable Object, V extends @Nullable Object> void deleteFromValueSet( 148 ValueSetLink<K, V> entry) { 149 succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); 150 } 151 152 private static <K extends @Nullable Object, V extends @Nullable Object> void deleteFromMultimap( 153 ValueEntry<K, V> entry) { 154 succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); 155 } 156 157 /** 158 * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the 159 * hash table for a {@code Set<V>} associated with a key, the linked list of insertion-ordered 160 * entries in that {@code Set<V>}, and the linked list of entries in the LinkedHashMultimap as a 161 * whole. 162 */ 163 @VisibleForTesting 164 static final class ValueEntry<K extends @Nullable Object, V extends @Nullable Object> 165 extends ImmutableEntry<K, V> implements ValueSetLink<K, V> { 166 final int smearedValueHash; 167 168 @Nullable ValueEntry<K, V> nextInValueBucket; 169 /* 170 * The *InValueSet and *InMultimap fields below are null after construction, but we almost 171 * always call succeedsIn*() to initialize them immediately thereafter. 172 * 173 * The exception is the *InValueSet fields of multimapHeaderEntry, which are never set. (That 174 * works out fine as long as we continue to be careful not to try to delete them or iterate 175 * past them.) 176 * 177 * We could consider "lying" and omitting @CheckNotNull from all these fields. Normally, I'm not 178 * a fan of that: What if we someday implement (presumably to be enabled during tests only) 179 * bytecode rewriting that checks for any null value that passes through an API with a 180 * known-non-null type? But that particular problem might not arise here, since we're not 181 * actually reading from the fields in any case in which they might be null (as proven by the 182 * requireNonNull checks below). Plus, we're *already* lying here, since newHeader passes a null 183 * key and value, which we pass to the superconstructor, even though the key and value type for 184 * a given entry might not include null. The right fix for the header problems is probably to 185 * define a separate MultimapLink interface with a separate "header" implementation, which 186 * hopefully could avoid implementing Entry or ValueSetLink at all. (But note that that approach 187 * requires us to define extra classes -- unfortunate under Android.) *Then* we could consider 188 * lying about the fields below on the grounds that we always initialize them just after the 189 * constructor -- an example of the kind of lying that our hypothetical bytecode rewriter would 190 * already have to deal with, thanks to DI frameworks that perform field and method injection, 191 * frameworks like Android that define post-construct hooks like Activity.onCreate, etc. 192 */ 193 194 private @Nullable ValueSetLink<K, V> predecessorInValueSet; 195 private @Nullable ValueSetLink<K, V> successorInValueSet; 196 197 private @Nullable ValueEntry<K, V> predecessorInMultimap; 198 private @Nullable ValueEntry<K, V> successorInMultimap; 199 200 ValueEntry( 201 @ParametricNullness K key, 202 @ParametricNullness V value, 203 int smearedValueHash, 204 @Nullable ValueEntry<K, V> nextInValueBucket) { 205 super(key, value); 206 this.smearedValueHash = smearedValueHash; 207 this.nextInValueBucket = nextInValueBucket; 208 } 209 210 @SuppressWarnings("nullness") // see the comment on the class fields, especially about newHeader 211 static <K extends @Nullable Object, V extends @Nullable Object> ValueEntry<K, V> newHeader() { 212 return new ValueEntry<>(null, null, 0, null); 213 } 214 215 boolean matchesValue(@Nullable Object v, int smearedVHash) { 216 return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); 217 } 218 219 @Override 220 public ValueSetLink<K, V> getPredecessorInValueSet() { 221 return requireNonNull(predecessorInValueSet); // see the comment on the class fields 222 } 223 224 @Override 225 public ValueSetLink<K, V> getSuccessorInValueSet() { 226 return requireNonNull(successorInValueSet); // see the comment on the class fields 227 } 228 229 @Override 230 public void setPredecessorInValueSet(ValueSetLink<K, V> entry) { 231 predecessorInValueSet = entry; 232 } 233 234 @Override 235 public void setSuccessorInValueSet(ValueSetLink<K, V> entry) { 236 successorInValueSet = entry; 237 } 238 239 public ValueEntry<K, V> getPredecessorInMultimap() { 240 return requireNonNull(predecessorInMultimap); // see the comment on the class fields 241 } 242 243 public ValueEntry<K, V> getSuccessorInMultimap() { 244 return requireNonNull(successorInMultimap); // see the comment on the class fields 245 } 246 247 public void setSuccessorInMultimap(ValueEntry<K, V> multimapSuccessor) { 248 this.successorInMultimap = multimapSuccessor; 249 } 250 251 public void setPredecessorInMultimap(ValueEntry<K, V> multimapPredecessor) { 252 this.predecessorInMultimap = multimapPredecessor; 253 } 254 } 255 256 private static final int DEFAULT_KEY_CAPACITY = 16; 257 private static final int DEFAULT_VALUE_SET_CAPACITY = 2; 258 @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0; 259 260 @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; 261 private transient ValueEntry<K, V> multimapHeaderEntry; 262 263 private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { 264 super(Platform.<K, Collection<V>>newLinkedHashMapWithExpectedSize(keyCapacity)); 265 checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); 266 267 this.valueSetCapacity = valueSetCapacity; 268 this.multimapHeaderEntry = ValueEntry.newHeader(); 269 succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 270 } 271 272 /** 273 * {@inheritDoc} 274 * 275 * <p>Creates an empty {@code LinkedHashSet} for a collection of values for one key. 276 * 277 * @return a new {@code LinkedHashSet} containing a collection of values for one key 278 */ 279 @Override 280 Set<V> createCollection() { 281 return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity); 282 } 283 284 /** 285 * {@inheritDoc} 286 * 287 * <p>Creates a decorated insertion-ordered set that also keeps track of the order in which 288 * key-value pairs are added to the multimap. 289 * 290 * @param key key to associate with values in the collection 291 * @return a new decorated set containing a collection of values for one key 292 */ 293 @Override 294 Collection<V> createCollection(@ParametricNullness K key) { 295 return new ValueSet(key, valueSetCapacity); 296 } 297 298 /** 299 * {@inheritDoc} 300 * 301 * <p>If {@code values} is not empty and the multimap already contains a mapping for {@code key}, 302 * the {@code keySet()} ordering is unchanged. However, the provided values always come last in 303 * the {@link #entries()} and {@link #values()} iteration orderings. 304 */ 305 @CanIgnoreReturnValue 306 @Override 307 public Set<V> replaceValues(@ParametricNullness K key, Iterable<? extends V> values) { 308 return super.replaceValues(key, values); 309 } 310 311 /** 312 * Returns a set of all key-value pairs. Changes to the returned set will update the underlying 313 * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll} 314 * operations. 315 * 316 * <p>The iterator generated by the returned set traverses the entries in the order they were 317 * added to the multimap. 318 * 319 * <p>Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the 320 * time the entry is returned by a method call to the collection or its iterator. 321 */ 322 @Override 323 public Set<Entry<K, V>> entries() { 324 return super.entries(); 325 } 326 327 /** 328 * Returns a view collection of all <i>distinct</i> keys contained in this multimap. Note that the 329 * key set contains a key if and only if this multimap maps that key to at least one value. 330 * 331 * <p>The iterator generated by the returned set traverses the keys in the order they were first 332 * added to the multimap. 333 * 334 * <p>Changes to the returned set will update the underlying multimap, and vice versa. However, 335 * <i>adding</i> to the returned set is not possible. 336 */ 337 @Override 338 public Set<K> keySet() { 339 return super.keySet(); 340 } 341 342 /** 343 * Returns a collection of all values in the multimap. Changes to the returned collection will 344 * update the underlying multimap, and vice versa. 345 * 346 * <p>The iterator generated by the returned collection traverses the values in the order they 347 * were added to the multimap. 348 */ 349 @Override 350 public Collection<V> values() { 351 return super.values(); 352 } 353 354 @VisibleForTesting 355 @WeakOuter 356 final class ValueSet extends Sets.ImprovedAbstractSet<V> implements ValueSetLink<K, V> { 357 /* 358 * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory 359 * consumption. 360 */ 361 362 @ParametricNullness private final K key; 363 @VisibleForTesting @Nullable ValueEntry<K, V>[] hashTable; 364 private int size = 0; 365 private int modCount = 0; 366 367 // We use the set object itself as the end of the linked list, avoiding an unnecessary 368 // entry object per key. 369 private ValueSetLink<K, V> firstEntry; 370 private ValueSetLink<K, V> lastEntry; 371 372 ValueSet(@ParametricNullness K key, int expectedValues) { 373 this.key = key; 374 this.firstEntry = this; 375 this.lastEntry = this; 376 // Round expected values up to a power of 2 to get the table size. 377 int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR); 378 379 @SuppressWarnings({"rawtypes", "unchecked"}) 380 @Nullable 381 ValueEntry<K, V>[] hashTable = new @Nullable ValueEntry[tableSize]; 382 this.hashTable = hashTable; 383 } 384 385 private int mask() { 386 return hashTable.length - 1; 387 } 388 389 @Override 390 public ValueSetLink<K, V> getPredecessorInValueSet() { 391 return lastEntry; 392 } 393 394 @Override 395 public ValueSetLink<K, V> getSuccessorInValueSet() { 396 return firstEntry; 397 } 398 399 @Override 400 public void setPredecessorInValueSet(ValueSetLink<K, V> entry) { 401 lastEntry = entry; 402 } 403 404 @Override 405 public void setSuccessorInValueSet(ValueSetLink<K, V> entry) { 406 firstEntry = entry; 407 } 408 409 @Override 410 public Iterator<V> iterator() { 411 return new Iterator<V>() { 412 ValueSetLink<K, V> nextEntry = firstEntry; 413 @Nullable ValueEntry<K, V> toRemove; 414 int expectedModCount = modCount; 415 416 private void checkForComodification() { 417 if (modCount != expectedModCount) { 418 throw new ConcurrentModificationException(); 419 } 420 } 421 422 @Override 423 public boolean hasNext() { 424 checkForComodification(); 425 return nextEntry != ValueSet.this; 426 } 427 428 @Override 429 @ParametricNullness 430 public V next() { 431 if (!hasNext()) { 432 throw new NoSuchElementException(); 433 } 434 ValueEntry<K, V> entry = (ValueEntry<K, V>) nextEntry; 435 V result = entry.getValue(); 436 toRemove = entry; 437 nextEntry = entry.getSuccessorInValueSet(); 438 return result; 439 } 440 441 @Override 442 public void remove() { 443 checkForComodification(); 444 checkState(toRemove != null, "no calls to next() since the last call to remove()"); 445 ValueSet.this.remove(toRemove.getValue()); 446 expectedModCount = modCount; 447 toRemove = null; 448 } 449 }; 450 } 451 452 @Override 453 public void forEach(Consumer<? super V> action) { 454 checkNotNull(action); 455 for (ValueSetLink<K, V> entry = firstEntry; 456 entry != ValueSet.this; 457 entry = entry.getSuccessorInValueSet()) { 458 action.accept(((ValueEntry<K, V>) entry).getValue()); 459 } 460 } 461 462 @Override 463 public int size() { 464 return size; 465 } 466 467 @Override 468 public boolean contains(@Nullable Object o) { 469 int smearedHash = Hashing.smearedHash(o); 470 for (ValueEntry<K, V> entry = hashTable[smearedHash & mask()]; 471 entry != null; 472 entry = entry.nextInValueBucket) { 473 if (entry.matchesValue(o, smearedHash)) { 474 return true; 475 } 476 } 477 return false; 478 } 479 480 @Override 481 public boolean add(@ParametricNullness V value) { 482 int smearedHash = Hashing.smearedHash(value); 483 int bucket = smearedHash & mask(); 484 ValueEntry<K, V> rowHead = hashTable[bucket]; 485 for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) { 486 if (entry.matchesValue(value, smearedHash)) { 487 return false; 488 } 489 } 490 491 ValueEntry<K, V> newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); 492 succeedsInValueSet(lastEntry, newEntry); 493 succeedsInValueSet(newEntry, this); 494 succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); 495 succeedsInMultimap(newEntry, multimapHeaderEntry); 496 hashTable[bucket] = newEntry; 497 size++; 498 modCount++; 499 rehashIfNecessary(); 500 return true; 501 } 502 503 private void rehashIfNecessary() { 504 if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { 505 @SuppressWarnings("unchecked") 506 ValueEntry<K, V>[] hashTable = 507 (ValueEntry<K, V>[]) new ValueEntry<?, ?>[this.hashTable.length * 2]; 508 this.hashTable = hashTable; 509 int mask = hashTable.length - 1; 510 for (ValueSetLink<K, V> entry = firstEntry; 511 entry != this; 512 entry = entry.getSuccessorInValueSet()) { 513 ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 514 int bucket = valueEntry.smearedValueHash & mask; 515 valueEntry.nextInValueBucket = hashTable[bucket]; 516 hashTable[bucket] = valueEntry; 517 } 518 } 519 } 520 521 @CanIgnoreReturnValue 522 @Override 523 public boolean remove(@Nullable Object o) { 524 int smearedHash = Hashing.smearedHash(o); 525 int bucket = smearedHash & mask(); 526 ValueEntry<K, V> prev = null; 527 for (ValueEntry<K, V> entry = hashTable[bucket]; 528 entry != null; 529 prev = entry, entry = entry.nextInValueBucket) { 530 if (entry.matchesValue(o, smearedHash)) { 531 if (prev == null) { 532 // first entry in the bucket 533 hashTable[bucket] = entry.nextInValueBucket; 534 } else { 535 prev.nextInValueBucket = entry.nextInValueBucket; 536 } 537 deleteFromValueSet(entry); 538 deleteFromMultimap(entry); 539 size--; 540 modCount++; 541 return true; 542 } 543 } 544 return false; 545 } 546 547 @Override 548 public void clear() { 549 Arrays.fill(hashTable, null); 550 size = 0; 551 for (ValueSetLink<K, V> entry = firstEntry; 552 entry != this; 553 entry = entry.getSuccessorInValueSet()) { 554 ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 555 deleteFromMultimap(valueEntry); 556 } 557 succeedsInValueSet(this, this); 558 modCount++; 559 } 560 } 561 562 @Override 563 Iterator<Entry<K, V>> entryIterator() { 564 return new Iterator<Entry<K, V>>() { 565 ValueEntry<K, V> nextEntry = multimapHeaderEntry.getSuccessorInMultimap(); 566 @Nullable ValueEntry<K, V> toRemove; 567 568 @Override 569 public boolean hasNext() { 570 return nextEntry != multimapHeaderEntry; 571 } 572 573 @Override 574 public Entry<K, V> next() { 575 if (!hasNext()) { 576 throw new NoSuchElementException(); 577 } 578 ValueEntry<K, V> result = nextEntry; 579 toRemove = result; 580 nextEntry = nextEntry.getSuccessorInMultimap(); 581 return result; 582 } 583 584 @Override 585 public void remove() { 586 checkState(toRemove != null, "no calls to next() since the last call to remove()"); 587 LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); 588 toRemove = null; 589 } 590 }; 591 } 592 593 @Override 594 Spliterator<Entry<K, V>> entrySpliterator() { 595 return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED); 596 } 597 598 @Override 599 Iterator<V> valueIterator() { 600 return Maps.valueIterator(entryIterator()); 601 } 602 603 @Override 604 Spliterator<V> valueSpliterator() { 605 return CollectSpliterators.map(entrySpliterator(), Entry::getValue); 606 } 607 608 @Override 609 public void clear() { 610 super.clear(); 611 succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 612 } 613 614 /** 615 * @serialData the expected values per key, the number of distinct keys, the number of entries, 616 * and the entries in order 617 */ 618 @GwtIncompatible // java.io.ObjectOutputStream 619 @J2ktIncompatible 620 private void writeObject(ObjectOutputStream stream) throws IOException { 621 stream.defaultWriteObject(); 622 stream.writeInt(keySet().size()); 623 for (K key : keySet()) { 624 stream.writeObject(key); 625 } 626 stream.writeInt(size()); 627 for (Entry<K, V> entry : entries()) { 628 stream.writeObject(entry.getKey()); 629 stream.writeObject(entry.getValue()); 630 } 631 } 632 633 @GwtIncompatible // java.io.ObjectInputStream 634 @J2ktIncompatible 635 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 636 stream.defaultReadObject(); 637 multimapHeaderEntry = ValueEntry.newHeader(); 638 succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 639 valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; 640 int distinctKeys = stream.readInt(); 641 Map<K, Collection<V>> map = Platform.newLinkedHashMapWithExpectedSize(12); 642 for (int i = 0; i < distinctKeys; i++) { 643 @SuppressWarnings("unchecked") 644 K key = (K) stream.readObject(); 645 map.put(key, createCollection(key)); 646 } 647 int entries = stream.readInt(); 648 for (int i = 0; i < entries; i++) { 649 @SuppressWarnings("unchecked") 650 K key = (K) stream.readObject(); 651 @SuppressWarnings("unchecked") 652 V value = (V) stream.readObject(); 653 /* 654 * requireNonNull is safe for a properly serialized multimap: We've already inserted a 655 * collection for each key that we expect. 656 */ 657 requireNonNull(map.get(key)).add(value); 658 } 659 setMap(map); 660 } 661 662 @GwtIncompatible // java serialization not supported 663 @J2ktIncompatible 664 private static final long serialVersionUID = 1; 665}