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