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