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 ValueEntry<K, V>[] hashTable = new @Nullable ValueEntry[tableSize]; 381 this.hashTable = hashTable; 382 } 383 384 private int mask() { 385 return hashTable.length - 1; 386 } 387 388 @Override 389 public ValueSetLink<K, V> getPredecessorInValueSet() { 390 return lastEntry; 391 } 392 393 @Override 394 public ValueSetLink<K, V> getSuccessorInValueSet() { 395 return firstEntry; 396 } 397 398 @Override 399 public void setPredecessorInValueSet(ValueSetLink<K, V> entry) { 400 lastEntry = entry; 401 } 402 403 @Override 404 public void setSuccessorInValueSet(ValueSetLink<K, V> entry) { 405 firstEntry = entry; 406 } 407 408 @Override 409 public Iterator<V> iterator() { 410 return new Iterator<V>() { 411 ValueSetLink<K, V> nextEntry = firstEntry; 412 @Nullable ValueEntry<K, V> toRemove; 413 int expectedModCount = modCount; 414 415 private void checkForComodification() { 416 if (modCount != expectedModCount) { 417 throw new ConcurrentModificationException(); 418 } 419 } 420 421 @Override 422 public boolean hasNext() { 423 checkForComodification(); 424 return nextEntry != ValueSet.this; 425 } 426 427 @Override 428 @ParametricNullness 429 public V next() { 430 if (!hasNext()) { 431 throw new NoSuchElementException(); 432 } 433 ValueEntry<K, V> entry = (ValueEntry<K, V>) nextEntry; 434 V result = entry.getValue(); 435 toRemove = entry; 436 nextEntry = entry.getSuccessorInValueSet(); 437 return result; 438 } 439 440 @Override 441 public void remove() { 442 checkForComodification(); 443 checkState(toRemove != null, "no calls to next() since the last call to remove()"); 444 ValueSet.this.remove(toRemove.getValue()); 445 expectedModCount = modCount; 446 toRemove = null; 447 } 448 }; 449 } 450 451 @Override 452 public void forEach(Consumer<? super V> action) { 453 checkNotNull(action); 454 for (ValueSetLink<K, V> entry = firstEntry; 455 entry != ValueSet.this; 456 entry = entry.getSuccessorInValueSet()) { 457 action.accept(((ValueEntry<K, V>) entry).getValue()); 458 } 459 } 460 461 @Override 462 public int size() { 463 return size; 464 } 465 466 @Override 467 public boolean contains(@Nullable Object o) { 468 int smearedHash = Hashing.smearedHash(o); 469 for (ValueEntry<K, V> entry = hashTable[smearedHash & mask()]; 470 entry != null; 471 entry = entry.nextInValueBucket) { 472 if (entry.matchesValue(o, smearedHash)) { 473 return true; 474 } 475 } 476 return false; 477 } 478 479 @Override 480 public boolean add(@ParametricNullness V value) { 481 int smearedHash = Hashing.smearedHash(value); 482 int bucket = smearedHash & mask(); 483 ValueEntry<K, V> rowHead = hashTable[bucket]; 484 for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) { 485 if (entry.matchesValue(value, smearedHash)) { 486 return false; 487 } 488 } 489 490 ValueEntry<K, V> newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); 491 succeedsInValueSet(lastEntry, newEntry); 492 succeedsInValueSet(newEntry, this); 493 succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); 494 succeedsInMultimap(newEntry, multimapHeaderEntry); 495 hashTable[bucket] = newEntry; 496 size++; 497 modCount++; 498 rehashIfNecessary(); 499 return true; 500 } 501 502 private void rehashIfNecessary() { 503 if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { 504 @SuppressWarnings("unchecked") 505 ValueEntry<K, V>[] hashTable = 506 (ValueEntry<K, V>[]) new ValueEntry<?, ?>[this.hashTable.length * 2]; 507 this.hashTable = hashTable; 508 int mask = hashTable.length - 1; 509 for (ValueSetLink<K, V> entry = firstEntry; 510 entry != this; 511 entry = entry.getSuccessorInValueSet()) { 512 ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 513 int bucket = valueEntry.smearedValueHash & mask; 514 valueEntry.nextInValueBucket = hashTable[bucket]; 515 hashTable[bucket] = valueEntry; 516 } 517 } 518 } 519 520 @CanIgnoreReturnValue 521 @Override 522 public boolean remove(@Nullable Object o) { 523 int smearedHash = Hashing.smearedHash(o); 524 int bucket = smearedHash & mask(); 525 ValueEntry<K, V> prev = null; 526 for (ValueEntry<K, V> entry = hashTable[bucket]; 527 entry != null; 528 prev = entry, entry = entry.nextInValueBucket) { 529 if (entry.matchesValue(o, smearedHash)) { 530 if (prev == null) { 531 // first entry in the bucket 532 hashTable[bucket] = entry.nextInValueBucket; 533 } else { 534 prev.nextInValueBucket = entry.nextInValueBucket; 535 } 536 deleteFromValueSet(entry); 537 deleteFromMultimap(entry); 538 size--; 539 modCount++; 540 return true; 541 } 542 } 543 return false; 544 } 545 546 @Override 547 public void clear() { 548 Arrays.fill(hashTable, null); 549 size = 0; 550 for (ValueSetLink<K, V> entry = firstEntry; 551 entry != this; 552 entry = entry.getSuccessorInValueSet()) { 553 ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 554 deleteFromMultimap(valueEntry); 555 } 556 succeedsInValueSet(this, this); 557 modCount++; 558 } 559 } 560 561 @Override 562 Iterator<Entry<K, V>> entryIterator() { 563 return new Iterator<Entry<K, V>>() { 564 ValueEntry<K, V> nextEntry = multimapHeaderEntry.getSuccessorInMultimap(); 565 @Nullable ValueEntry<K, V> toRemove; 566 567 @Override 568 public boolean hasNext() { 569 return nextEntry != multimapHeaderEntry; 570 } 571 572 @Override 573 public Entry<K, V> next() { 574 if (!hasNext()) { 575 throw new NoSuchElementException(); 576 } 577 ValueEntry<K, V> result = nextEntry; 578 toRemove = result; 579 nextEntry = nextEntry.getSuccessorInMultimap(); 580 return result; 581 } 582 583 @Override 584 public void remove() { 585 checkState(toRemove != null, "no calls to next() since the last call to remove()"); 586 LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); 587 toRemove = null; 588 } 589 }; 590 } 591 592 @Override 593 Spliterator<Entry<K, V>> entrySpliterator() { 594 return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED); 595 } 596 597 @Override 598 Iterator<V> valueIterator() { 599 return Maps.valueIterator(entryIterator()); 600 } 601 602 @Override 603 Spliterator<V> valueSpliterator() { 604 return CollectSpliterators.map(entrySpliterator(), Entry::getValue); 605 } 606 607 @Override 608 public void clear() { 609 super.clear(); 610 succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 611 } 612 613 /** 614 * @serialData the expected values per key, the number of distinct keys, the number of entries, 615 * and the entries in order 616 */ 617 @GwtIncompatible // java.io.ObjectOutputStream 618 @J2ktIncompatible 619 private void writeObject(ObjectOutputStream stream) throws IOException { 620 stream.defaultWriteObject(); 621 stream.writeInt(keySet().size()); 622 for (K key : keySet()) { 623 stream.writeObject(key); 624 } 625 stream.writeInt(size()); 626 for (Entry<K, V> entry : entries()) { 627 stream.writeObject(entry.getKey()); 628 stream.writeObject(entry.getValue()); 629 } 630 } 631 632 @GwtIncompatible // java.io.ObjectInputStream 633 @J2ktIncompatible 634 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 635 stream.defaultReadObject(); 636 multimapHeaderEntry = ValueEntry.newHeader(); 637 succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 638 valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; 639 int distinctKeys = stream.readInt(); 640 Map<K, Collection<V>> map = Platform.newLinkedHashMapWithExpectedSize(12); 641 for (int i = 0; i < distinctKeys; i++) { 642 @SuppressWarnings("unchecked") 643 K key = (K) stream.readObject(); 644 map.put(key, createCollection(key)); 645 } 646 int entries = stream.readInt(); 647 for (int i = 0; i < entries; i++) { 648 @SuppressWarnings("unchecked") 649 K key = (K) stream.readObject(); 650 @SuppressWarnings("unchecked") 651 V value = (V) stream.readObject(); 652 /* 653 * requireNonNull is safe for a properly serialized multimap: We've already inserted a 654 * collection for each key that we expect. 655 */ 656 requireNonNull(map.get(key)).add(value); 657 } 658 setMap(map); 659 } 660 661 @GwtIncompatible // java serialization not supported 662 @J2ktIncompatible 663 private static final long serialVersionUID = 1; 664}