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}