View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.map;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.Serializable;
23  import java.util.AbstractCollection;
24  import java.util.AbstractSet;
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Objects;
30  import java.util.Set;
31  
32  import org.apache.commons.collections4.CollectionUtils;
33  import org.apache.commons.collections4.IterableMap;
34  import org.apache.commons.collections4.MapIterator;
35  import org.apache.commons.collections4.ResettableIterator;
36  import org.apache.commons.collections4.iterators.EmptyIterator;
37  import org.apache.commons.collections4.iterators.EmptyMapIterator;
38  
39  /**
40   * A {@code Map} implementation that stores data in simple fields until
41   * the size is greater than 3.
42   * <p>
43   * This map is designed for performance and can outstrip HashMap.
44   * It also has good garbage collection characteristics.
45   * </p>
46   * <ul>
47   * <li>Optimised for operation at size 3 or less.
48   * <li>Still works well once size 3 exceeded.
49   * <li>Gets at size 3 or less are about 0-10% faster than HashMap,
50   * <li>Puts at size 3 or less are over 4 times faster than HashMap.
51   * <li>Performance 5% slower than HashMap once size 3 exceeded once.
52   * </ul>
53   * <p>
54   * The design uses two distinct modes of operation - flat and delegate.
55   * While the map is size 3 or less, operations map straight onto fields using
56   * switch statements. Once size 4 is reached, the map switches to delegate mode
57   * and only switches back when cleared. In delegate mode, all operations are
58   * forwarded straight to a HashMap resulting in the 5% performance loss.
59   * </p>
60   * <p>
61   * The performance gains on puts are due to not needing to create a Map Entry
62   * object. This is a large saving not only in performance but in garbage collection.
63   * </p>
64   * <p>
65   * Whilst in flat mode this map is also easy for the garbage collector to dispatch.
66   * This is because it contains no complex objects or arrays which slow the progress.
67   * </p>
68   * <p>
69   * Do not use {@code Flat3Map} if the size is likely to grow beyond 3.
70   * </p>
71   * <p>
72   * <strong>Note that Flat3Map is not synchronized and is not thread-safe.</strong>
73   * If you wish to use this map from multiple threads concurrently, you must use
74   * appropriate synchronization. The simplest approach is to wrap this map
75   * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
76   * exceptions when accessed by concurrent threads without synchronization.
77   * </p>
78   *
79   * @param <K> the type of the keys in this map
80   * @param <V> the type of the values in this map
81   * @since 3.0
82   */
83  public class Flat3Map<K, V> implements IterableMap<K, V>, Serializable, Cloneable {
84  
85      abstract static class EntryIterator<K, V> {
86          private final Flat3Map<K, V> parent;
87          private int nextIndex;
88          private FlatMapEntry<K, V> currentEntry;
89  
90          /**
91           * Create a new Flat3Map.EntryIterator.
92           */
93          EntryIterator(final Flat3Map<K, V> parent) {
94              this.parent = parent;
95          }
96  
97          public boolean hasNext() {
98              return nextIndex < parent.size;
99          }
100 
101         public Map.Entry<K, V> nextEntry() {
102             if (!hasNext()) {
103                 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
104             }
105             currentEntry = new FlatMapEntry<>(parent, ++nextIndex);
106             return currentEntry;
107         }
108 
109         public void remove() {
110             if (currentEntry == null) {
111                 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
112             }
113             parent.remove(currentEntry.getKey());
114             currentEntry.setRemoved(true);
115             nextIndex--;
116             currentEntry = null;
117         }
118 
119     }
120 
121     /**
122      * EntrySet
123      */
124     static class EntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
125         private final Flat3Map<K, V> parent;
126 
127         EntrySet(final Flat3Map<K, V> parent) {
128             this.parent = parent;
129         }
130 
131         @Override
132         public void clear() {
133             parent.clear();
134         }
135 
136         @Override
137         public Iterator<Map.Entry<K, V>> iterator() {
138             if (parent.delegateMap != null) {
139                 return parent.delegateMap.entrySet().iterator();
140             }
141             if (parent.isEmpty()) {
142                 return EmptyIterator.<Map.Entry<K, V>>emptyIterator();
143             }
144             return new EntrySetIterator<>(parent);
145         }
146 
147         @Override
148         public boolean remove(final Object obj) {
149             if (!(obj instanceof Map.Entry)) {
150                 return false;
151             }
152             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
153             final Object key = entry.getKey();
154             final boolean result = parent.containsKey(key);
155             parent.remove(key);
156             return result;
157         }
158 
159         @Override
160         public int size() {
161             return parent.size();
162         }
163     }
164     /**
165      * EntrySetIterator and MapEntry
166      */
167     static class EntrySetIterator<K, V> extends EntryIterator<K, V> implements Iterator<Map.Entry<K, V>> {
168         EntrySetIterator(final Flat3Map<K, V> parent) {
169             super(parent);
170         }
171 
172         @Override
173         public Map.Entry<K, V> next() {
174             return nextEntry();
175         }
176     }
177     static class FlatMapEntry<K, V> implements Map.Entry<K, V> {
178         private final Flat3Map<K, V> parent;
179         private final int index;
180         private volatile boolean removed;
181 
182         FlatMapEntry(final Flat3Map<K, V> parent, final int index) {
183             this.parent = parent;
184             this.index = index;
185             this.removed = false;
186         }
187 
188         @Override
189         public boolean equals(final Object obj) {
190             if (removed) {
191                 return false;
192             }
193             if (!(obj instanceof Map.Entry)) {
194                 return false;
195             }
196             final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
197             return Objects.equals(getKey(), other.getKey()) &&
198                    Objects.equals(getValue(), other.getValue());
199         }
200 
201         @Override
202         public K getKey() {
203             if (removed) {
204                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
205             }
206             switch (index) {
207             case 3:
208                 return parent.key3;
209             case 2:
210                 return parent.key2;
211             case 1:
212                 return parent.key1;
213             }
214             throw new IllegalStateException("Invalid map index: " + index);
215         }
216 
217         @Override
218         public V getValue() {
219             if (removed) {
220                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
221             }
222             switch (index) {
223             case 3:
224                 return parent.value3;
225             case 2:
226                 return parent.value2;
227             case 1:
228                 return parent.value1;
229             }
230             throw new IllegalStateException("Invalid map index: " + index);
231         }
232 
233         @Override
234         public int hashCode() {
235             if (removed) {
236                 return 0;
237             }
238             final Object key = getKey();
239             final Object value = getValue();
240             return (key == null ? 0 : key.hashCode()) ^
241                    (value == null ? 0 : value.hashCode());
242         }
243 
244         /**
245          * Used by the iterator that created this entry to indicate that
246          * {@link java.util.Iterator#remove()} has been called.
247          * <p>
248          * As a consequence, all subsequent call to {@link #getKey()},
249          * {@link #setValue(Object)} and {@link #getValue()} will fail.
250          *
251          * @param removed the new value of the removed flag
252          */
253         void setRemoved(final boolean removed) {
254             this.removed = removed;
255         }
256 
257         @Override
258         public V setValue(final V value) {
259             if (removed) {
260                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
261             }
262             final V old = getValue();
263             switch (index) {
264             case 3:
265                 parent.value3 = value;
266                 break;
267             case 2:
268                 parent.value2 = value;
269                 break;
270             case 1:
271                 parent.value1 = value;
272                 break;
273             default:
274                 throw new IllegalStateException("Invalid map index: " + index);
275             }
276             return old;
277         }
278 
279         @Override
280         public String toString() {
281             if (!removed) {
282                 return getKey() + "=" + getValue();
283             }
284             return "";
285         }
286 
287     }
288 
289     /**
290      * FlatMapIterator
291      */
292     static class FlatMapIterator<K, V> implements MapIterator<K, V>, ResettableIterator<K> {
293         private final Flat3Map<K, V> parent;
294         private int nextIndex;
295         private boolean canRemove;
296 
297         FlatMapIterator(final Flat3Map<K, V> parent) {
298             this.parent = parent;
299         }
300 
301         @Override
302         public K getKey() {
303             if (!canRemove) {
304                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
305             }
306             switch (nextIndex) {
307             case 3:
308                 return parent.key3;
309             case 2:
310                 return parent.key2;
311             case 1:
312                 return parent.key1;
313             }
314             throw new IllegalStateException("Invalid map index: " + nextIndex);
315         }
316 
317         @Override
318         public V getValue() {
319             if (!canRemove) {
320                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
321             }
322             switch (nextIndex) {
323             case 3:
324                 return parent.value3;
325             case 2:
326                 return parent.value2;
327             case 1:
328                 return parent.value1;
329             }
330             throw new IllegalStateException("Invalid map index: " + nextIndex);
331         }
332 
333         @Override
334         public boolean hasNext() {
335             return nextIndex < parent.size;
336         }
337 
338         @Override
339         public K next() {
340             if (!hasNext()) {
341                 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
342             }
343             canRemove = true;
344             nextIndex++;
345             return getKey();
346         }
347 
348         @Override
349         public void remove() {
350             if (!canRemove) {
351                 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
352             }
353             parent.remove(getKey());
354             nextIndex--;
355             canRemove = false;
356         }
357 
358         @Override
359         public void reset() {
360             nextIndex = 0;
361             canRemove = false;
362         }
363 
364         @Override
365         public V setValue(final V value) {
366             if (!canRemove) {
367                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
368             }
369             final V old = getValue();
370             switch (nextIndex) {
371             case 3:
372                 parent.value3 = value;
373                 break;
374             case 2:
375                 parent.value2 = value;
376                 break;
377             case 1:
378                 parent.value1 = value;
379                 break;
380             default:
381                 throw new IllegalStateException("Invalid map index: " + nextIndex);
382             }
383             return old;
384         }
385 
386         @Override
387         public String toString() {
388             if (canRemove) {
389                 return "Iterator[" + getKey() + "=" + getValue() + "]";
390             }
391             return "Iterator[]";
392         }
393     }
394 
395     /**
396      * KeySet
397      */
398     static class KeySet<K> extends AbstractSet<K> {
399 
400         private final Flat3Map<K, ?> parent;
401 
402         KeySet(final Flat3Map<K, ?> parent) {
403             this.parent = parent;
404         }
405 
406         @Override
407         public void clear() {
408             parent.clear();
409         }
410 
411         @Override
412         public boolean contains(final Object key) {
413             return parent.containsKey(key);
414         }
415 
416         @Override
417         public Iterator<K> iterator() {
418             if (parent.delegateMap != null) {
419                 return parent.delegateMap.keySet().iterator();
420             }
421             if (parent.isEmpty()) {
422                 return EmptyIterator.<K>emptyIterator();
423             }
424             return new KeySetIterator<>(parent);
425         }
426 
427         @Override
428         public boolean remove(final Object key) {
429             final boolean result = parent.containsKey(key);
430             parent.remove(key);
431             return result;
432         }
433 
434         @Override
435         public int size() {
436             return parent.size();
437         }
438     }
439 
440     /**
441      * KeySetIterator
442      */
443     static class KeySetIterator<K> extends EntryIterator<K, Object> implements Iterator<K> {
444 
445         @SuppressWarnings("unchecked")
446         KeySetIterator(final Flat3Map<K, ?> parent) {
447             super((Flat3Map<K, Object>) parent);
448         }
449 
450         @Override
451         public K next() {
452             return nextEntry().getKey();
453         }
454     }
455 
456     /**
457      * Values
458      */
459     static class Values<V> extends AbstractCollection<V> {
460 
461         private final Flat3Map<?, V> parent;
462 
463         Values(final Flat3Map<?, V> parent) {
464             this.parent = parent;
465         }
466 
467         @Override
468         public void clear() {
469             parent.clear();
470         }
471 
472         @Override
473         public boolean contains(final Object value) {
474             return parent.containsValue(value);
475         }
476 
477         @Override
478         public Iterator<V> iterator() {
479             if (parent.delegateMap != null) {
480                 return parent.delegateMap.values().iterator();
481             }
482             if (parent.isEmpty()) {
483                 return EmptyIterator.<V>emptyIterator();
484             }
485             return new ValuesIterator<>(parent);
486         }
487 
488         @Override
489         public int size() {
490             return parent.size();
491         }
492     }
493 
494     /**
495      * ValuesIterator
496      */
497     static class ValuesIterator<V> extends EntryIterator<Object, V> implements Iterator<V> {
498 
499         @SuppressWarnings("unchecked")
500         ValuesIterator(final Flat3Map<?, V> parent) {
501             super((Flat3Map<Object, V>) parent);
502         }
503 
504         @Override
505         public V next() {
506             return nextEntry().getValue();
507         }
508     }
509 
510     /** Serialization version */
511     private static final long serialVersionUID = -6701087419741928296L;
512 
513     /** The size of the map, used while in flat mode */
514     private transient int size;
515 
516     /** Hash, used while in flat mode */
517     private transient int hash1;
518 
519     /** Hash, used while in flat mode */
520     private transient int hash2;
521 
522     /** Hash, used while in flat mode */
523     private transient int hash3;
524 
525     /** Key, used while in flat mode */
526     private transient K key1;
527 
528     /** Key, used while in flat mode */
529     private transient K key2;
530 
531     /** Key, used while in flat mode */
532     private transient K key3;
533 
534     /** Value, used while in flat mode */
535     private transient V value1;
536 
537     /** Value, used while in flat mode */
538     private transient V value2;
539 
540     /** Value, used while in flat mode */
541     private transient V value3;
542 
543     /** Map, used while in delegate mode */
544     private transient AbstractHashedMap<K, V> delegateMap;
545 
546     /**
547      * Constructs a new instance.
548      */
549     public Flat3Map() {
550     }
551 
552     /**
553      * Constructor copying elements from another map.
554      *
555      * @param map  the map to copy
556      * @throws NullPointerException if the map is null
557      */
558     public Flat3Map(final Map<? extends K, ? extends V> map) {
559         putAll(map);
560     }
561 
562     /**
563      * Clears the map, resetting the size to zero and nullifying references
564      * to avoid garbage collection issues.
565      */
566     @Override
567     public void clear() {
568         if (delegateMap != null) {
569             delegateMap.clear();  // should aid gc
570             delegateMap = null;  // switch back to flat mode
571         } else {
572             size = 0;
573             hash1 = hash2 = hash3 = 0;
574             key1 = key2 = key3 = null;
575             value1 = value2 = value3 = null;
576         }
577     }
578 
579     /**
580      * Clones the map without cloning the keys or values.
581      *
582      * @return a shallow clone
583      * @since 3.1
584      */
585     @Override
586     @SuppressWarnings("unchecked")
587     public Flat3Map<K, V> clone() {
588         try {
589             final Flat3Map<K, V> cloned = (Flat3Map<K, V>) super.clone();
590             if (cloned.delegateMap != null) {
591                 cloned.delegateMap = cloned.delegateMap.clone();
592             }
593             return cloned;
594         } catch (final CloneNotSupportedException ex) {
595             throw new UnsupportedOperationException(ex);
596         }
597     }
598 
599     /**
600      * Checks whether the map contains the specified key.
601      *
602      * @param key  the key to search for
603      * @return true if the map contains the key
604      */
605     @Override
606     public boolean containsKey(final Object key) {
607         if (delegateMap != null) {
608             return delegateMap.containsKey(key);
609         }
610         if (key == null) {
611             switch (size) {  // drop through
612             case 3:
613                 if (key3 == null) {
614                     return true;
615                 }
616             case 2:
617                 if (key2 == null) {
618                     return true;
619                 }
620             case 1:
621                 if (key1 == null) {
622                     return true;
623                 }
624             }
625         } else if (size > 0) {
626             final int hashCode = key.hashCode();
627             switch (size) {  // drop through
628             case 3:
629                 if (hash3 == hashCode && key.equals(key3)) {
630                     return true;
631                 }
632             case 2:
633                 if (hash2 == hashCode && key.equals(key2)) {
634                     return true;
635                 }
636             case 1:
637                 if (hash1 == hashCode && key.equals(key1)) {
638                     return true;
639                 }
640             }
641         }
642         return false;
643     }
644 
645     /**
646      * Checks whether the map contains the specified value.
647      *
648      * @param value  the value to search for
649      * @return true if the map contains the key
650      */
651     @Override
652     public boolean containsValue(final Object value) {
653         if (delegateMap != null) {
654             return delegateMap.containsValue(value);
655         }
656         if (value == null) {  // drop through
657             switch (size) {
658             case 3:
659                 if (value3 == null) {
660                     return true;
661                 }
662             case 2:
663                 if (value2 == null) {
664                     return true;
665                 }
666             case 1:
667                 if (value1 == null) {
668                     return true;
669                 }
670             }
671         } else {
672             switch (size) {  // drop through
673             case 3:
674                 if (value.equals(value3)) {
675                     return true;
676                 }
677             case 2:
678                 if (value.equals(value2)) {
679                     return true;
680                 }
681             case 1:
682                 if (value.equals(value1)) {
683                     return true;
684                 }
685             }
686         }
687         return false;
688     }
689 
690     /**
691      * Converts the flat map data to a map.
692      */
693     private void convertToMap() {
694         delegateMap = createDelegateMap();
695         switch (size) {  // drop through
696         case 3:
697             delegateMap.put(key3, value3);
698         case 2:
699             delegateMap.put(key2, value2);
700         case 1:
701             delegateMap.put(key1, value1);
702         case 0:
703             break;
704         default:
705             throw new IllegalStateException("Invalid map index: " + size);
706         }
707 
708         size = 0;
709         hash1 = hash2 = hash3 = 0;
710         key1 = key2 = key3 = null;
711         value1 = value2 = value3 = null;
712     }
713 
714     /**
715      * Create an instance of the map used for storage when in delegation mode.
716      * <p>
717      * This can be overridden by subclasses to provide a different map implementation.
718      * Not every AbstractHashedMap is suitable, identity and reference based maps
719      * would be poor choices.
720      * </p>
721      *
722      * @return a new AbstractHashedMap or subclass
723      * @since 3.1
724      */
725     protected AbstractHashedMap<K, V> createDelegateMap() {
726         return new HashedMap<>();
727     }
728 
729     /**
730      * Gets the entrySet view of the map.
731      * Changes made to the view affect this map.
732      * <p>
733      * NOTE: from 4.0, the returned Map Entry will be an independent object and will
734      * not change anymore as the iterator progresses. To avoid this additional object
735      * creation and simply iterate through the entries, use {@link #mapIterator()}.
736      * </p>
737      *
738      * @return the entrySet view
739      */
740     @Override
741     public Set<Map.Entry<K, V>> entrySet() {
742         if (delegateMap != null) {
743             return delegateMap.entrySet();
744         }
745         return new EntrySet<>(this);
746     }
747 
748     /**
749      * Compares this map with another.
750      *
751      * @param obj  the object to compare to
752      * @return true if equal
753      */
754     @Override
755     public boolean equals(final Object obj) {
756         if (obj == this) {
757             return true;
758         }
759         if (delegateMap != null) {
760             return delegateMap.equals(obj);
761         }
762         if (!(obj instanceof Map)) {
763             return false;
764         }
765         final Map<?, ?> other = (Map<?, ?>) obj;
766         if (size != other.size()) {
767             return false;
768         }
769         if (size > 0) {
770             Object otherValue = null;
771             switch (size) {  // drop through
772             case 3:
773                 if (!other.containsKey(key3)) {
774                     return false;
775                 }
776                 otherValue = other.get(key3);
777                 if (!Objects.equals(value3, otherValue)) {
778                     return false;
779                 }
780             case 2:
781                 if (!other.containsKey(key2)) {
782                     return false;
783                 }
784                 otherValue = other.get(key2);
785                 if (!Objects.equals(value2, otherValue)) {
786                     return false;
787                 }
788             case 1:
789                 if (!other.containsKey(key1)) {
790                     return false;
791                 }
792                 otherValue = other.get(key1);
793                 if (!Objects.equals(value1, otherValue)) {
794                     return false;
795                 }
796             }
797         }
798         return true;
799     }
800 
801     /**
802      * Gets the value mapped to the key specified.
803      *
804      * @param key  the key
805      * @return the mapped value, null if no match
806      */
807     @Override
808     public V get(final Object key) {
809         if (delegateMap != null) {
810             return delegateMap.get(key);
811         }
812         if (key == null) {
813             switch (size) {
814             // drop through
815             case 3:
816                 if (key3 == null) {
817                     return value3;
818                 }
819             case 2:
820                 if (key2 == null) {
821                     return value2;
822                 }
823             case 1:
824                 if (key1 == null) {
825                     return value1;
826                 }
827             }
828         } else if (size > 0) {
829             final int hashCode = key.hashCode();
830             switch (size) {
831             // drop through
832             case 3:
833                 if (hash3 == hashCode && key.equals(key3)) {
834                     return value3;
835                 }
836             case 2:
837                 if (hash2 == hashCode && key.equals(key2)) {
838                     return value2;
839                 }
840             case 1:
841                 if (hash1 == hashCode && key.equals(key1)) {
842                     return value1;
843                 }
844             }
845         }
846         return null;
847     }
848 
849     /**
850      * Gets the standard Map hashCode.
851      *
852      * @return the hash code defined in the Map interface
853      */
854     @Override
855     public int hashCode() {
856         if (delegateMap != null) {
857             return delegateMap.hashCode();
858         }
859         int total = 0;
860         switch (size) {  // drop through
861         case 3:
862             total += hash3 ^ (value3 == null ? 0 : value3.hashCode());
863         case 2:
864             total += hash2 ^ (value2 == null ? 0 : value2.hashCode());
865         case 1:
866             total += hash1 ^ (value1 == null ? 0 : value1.hashCode());
867         case 0:
868             break;
869         default:
870             throw new IllegalStateException("Invalid map index: " + size);
871         }
872         return total;
873     }
874 
875     /**
876      * Checks whether the map is currently empty.
877      *
878      * @return true if the map is currently size zero
879      */
880     @Override
881     public boolean isEmpty() {
882         return size() == 0;
883     }
884 
885     /**
886      * Gets the keySet view of the map.
887      * Changes made to the view affect this map.
888      * To simply iterate through the keys, use {@link #mapIterator()}.
889      *
890      * @return the keySet view
891      */
892     @Override
893     public Set<K> keySet() {
894         if (delegateMap != null) {
895             return delegateMap.keySet();
896         }
897         return new KeySet<>(this);
898     }
899 
900     /**
901      * Gets an iterator over the map.
902      * Changes made to the iterator affect this map.
903      * <p>
904      * A MapIterator returns the keys in the map. It also provides convenient
905      * methods to get the key and value, and set the value.
906      * It avoids the need to create an entrySet/keySet/values object.
907      * It also avoids creating the Map Entry object.
908      * </p>
909      *
910      * @return the map iterator
911      */
912     @Override
913     public MapIterator<K, V> mapIterator() {
914         if (delegateMap != null) {
915             return delegateMap.mapIterator();
916         }
917         if (size == 0) {
918             return EmptyMapIterator.<K, V>emptyMapIterator();
919         }
920         return new FlatMapIterator<>(this);
921     }
922 
923     /**
924      * Puts a key-value mapping into this map.
925      *
926      * @param key  the key to add
927      * @param value  the value to add
928      * @return the value previously mapped to this key, null if none
929      */
930     @Override
931     public V put(final K key, final V value) {
932         if (delegateMap != null) {
933             return delegateMap.put(key, value);
934         }
935         // change existing mapping
936         if (key == null) {
937             switch (size) {  // drop through
938             case 3:
939                 if (key3 == null) {
940                     final V old = value3;
941                     value3 = value;
942                     return old;
943                 }
944             case 2:
945                 if (key2 == null) {
946                     final V old = value2;
947                     value2 = value;
948                     return old;
949                 }
950             case 1:
951                 if (key1 == null) {
952                     final V old = value1;
953                     value1 = value;
954                     return old;
955                 }
956             }
957         } else if (size > 0) {
958             final int hashCode = key.hashCode();
959             switch (size) {  // drop through
960             case 3:
961                 if (hash3 == hashCode && key.equals(key3)) {
962                     final V old = value3;
963                     value3 = value;
964                     return old;
965                 }
966             case 2:
967                 if (hash2 == hashCode && key.equals(key2)) {
968                     final V old = value2;
969                     value2 = value;
970                     return old;
971                 }
972             case 1:
973                 if (hash1 == hashCode && key.equals(key1)) {
974                     final V old = value1;
975                     value1 = value;
976                     return old;
977                 }
978             }
979         }
980 
981         // add new mapping
982         switch (size) {
983         default:
984             convertToMap();
985             delegateMap.put(key, value);
986             return null;
987         case 2:
988             hash3 = key == null ? 0 : key.hashCode();
989             key3 = key;
990             value3 = value;
991             break;
992         case 1:
993             hash2 = key == null ? 0 : key.hashCode();
994             key2 = key;
995             value2 = value;
996             break;
997         case 0:
998             hash1 = key == null ? 0 : key.hashCode();
999             key1 = key;
1000             value1 = value;
1001             break;
1002         }
1003         size++;
1004         return null;
1005     }
1006 
1007     /**
1008      * Puts all the values from the specified map into this map.
1009      *
1010      * @param map  the map to add
1011      * @throws NullPointerException if the map is null
1012      */
1013     @Override
1014     public void putAll(final Map<? extends K, ? extends V> map) {
1015         final int size = map.size();
1016         if (size == 0) {
1017             return;
1018         }
1019         if (delegateMap != null) {
1020             delegateMap.putAll(map);
1021             return;
1022         }
1023         if (size < 4) {
1024             for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
1025                 put(entry.getKey(), entry.getValue());
1026             }
1027         } else {
1028             convertToMap();
1029             delegateMap.putAll(map);
1030         }
1031     }
1032 
1033     /**
1034      * Deserializes the map in using a custom routine.
1035      *
1036      * @param in the input stream
1037      * @throws IOException if an error occurs while reading from the stream
1038      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
1039      */
1040     @SuppressWarnings("unchecked")
1041     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1042         in.defaultReadObject();
1043         final int count = in.readInt();
1044         if (count > 3) {
1045             delegateMap = createDelegateMap();
1046         }
1047         for (int i = count; i > 0; i--) {
1048             put((K) in.readObject(), (V) in.readObject());
1049         }
1050     }
1051 
1052     /**
1053      * Removes the specified mapping from this map.
1054      *
1055      * @param key  the mapping to remove
1056      * @return the value mapped to the removed key, null if key not in map
1057      */
1058     @Override
1059     public V remove(final Object key) {
1060         if (delegateMap != null) {
1061             return delegateMap.remove(key);
1062         }
1063         if (size == 0) {
1064             return null;
1065         }
1066         if (key == null) {
1067             switch (size) {  // drop through
1068             case 3:
1069                 if (key3 == null) {
1070                     final V old = value3;
1071                     hash3 = 0;
1072                     key3 = null;
1073                     value3 = null;
1074                     size = 2;
1075                     return old;
1076                 }
1077                 if (key2 == null) {
1078                     final V old = value2;
1079                     hash2 = hash3;
1080                     key2 = key3;
1081                     value2 = value3;
1082                     hash3 = 0;
1083                     key3 = null;
1084                     value3 = null;
1085                     size = 2;
1086                     return old;
1087                 }
1088                 if (key1 == null) {
1089                     final V old = value1;
1090                     hash1 = hash3;
1091                     key1 = key3;
1092                     value1 = value3;
1093                     hash3 = 0;
1094                     key3 = null;
1095                     value3 = null;
1096                     size = 2;
1097                     return old;
1098                 }
1099                 return null;
1100             case 2:
1101                 if (key2 == null) {
1102                     final V old = value2;
1103                     hash2 = 0;
1104                     key2 = null;
1105                     value2 = null;
1106                     size = 1;
1107                     return old;
1108                 }
1109                 if (key1 == null) {
1110                     final V old = value1;
1111                     hash1 = hash2;
1112                     key1 = key2;
1113                     value1 = value2;
1114                     hash2 = 0;
1115                     key2 = null;
1116                     value2 = null;
1117                     size = 1;
1118                     return old;
1119                 }
1120                 return null;
1121             case 1:
1122                 if (key1 == null) {
1123                     final V old = value1;
1124                     hash1 = 0;
1125                     key1 = null;
1126                     value1 = null;
1127                     size = 0;
1128                     return old;
1129                 }
1130             }
1131         } else if (size > 0) {
1132             final int hashCode = key.hashCode();
1133             switch (size) {  // drop through
1134             case 3:
1135                 if (hash3 == hashCode && key.equals(key3)) {
1136                     final V old = value3;
1137                     hash3 = 0;
1138                     key3 = null;
1139                     value3 = null;
1140                     size = 2;
1141                     return old;
1142                 }
1143                 if (hash2 == hashCode && key.equals(key2)) {
1144                     final V old = value2;
1145                     hash2 = hash3;
1146                     key2 = key3;
1147                     value2 = value3;
1148                     hash3 = 0;
1149                     key3 = null;
1150                     value3 = null;
1151                     size = 2;
1152                     return old;
1153                 }
1154                 if (hash1 == hashCode && key.equals(key1)) {
1155                     final V old = value1;
1156                     hash1 = hash3;
1157                     key1 = key3;
1158                     value1 = value3;
1159                     hash3 = 0;
1160                     key3 = null;
1161                     value3 = null;
1162                     size = 2;
1163                     return old;
1164                 }
1165                 return null;
1166             case 2:
1167                 if (hash2 == hashCode && key.equals(key2)) {
1168                     final V old = value2;
1169                     hash2 = 0;
1170                     key2 = null;
1171                     value2 = null;
1172                     size = 1;
1173                     return old;
1174                 }
1175                 if (hash1 == hashCode && key.equals(key1)) {
1176                     final V old = value1;
1177                     hash1 = hash2;
1178                     key1 = key2;
1179                     value1 = value2;
1180                     hash2 = 0;
1181                     key2 = null;
1182                     value2 = null;
1183                     size = 1;
1184                     return old;
1185                 }
1186                 return null;
1187             case 1:
1188                 if (hash1 == hashCode && key.equals(key1)) {
1189                     final V old = value1;
1190                     hash1 = 0;
1191                     key1 = null;
1192                     value1 = null;
1193                     size = 0;
1194                     return old;
1195                 }
1196             }
1197         }
1198         return null;
1199     }
1200 
1201     /**
1202      * Gets the size of the map.
1203      *
1204      * @return the size
1205      */
1206     @Override
1207     public int size() {
1208         if (delegateMap != null) {
1209             return delegateMap.size();
1210         }
1211         return size;
1212     }
1213 
1214     /**
1215      * Gets the map as a String.
1216      *
1217      * @return a string version of the map
1218      */
1219     @Override
1220     public String toString() {
1221         if (delegateMap != null) {
1222             return delegateMap.toString();
1223         }
1224         if (size == 0) {
1225             return "{}";
1226         }
1227         final StringBuilder buf = new StringBuilder(128);
1228         buf.append('{');
1229         switch (size) {  // drop through
1230         case 3:
1231             buf.append(key3 == this ? "(this Map)" : key3);
1232             buf.append('=');
1233             buf.append(value3 == this ? "(this Map)" : value3);
1234             buf.append(CollectionUtils.COMMA);
1235         case 2:
1236             buf.append(key2 == this ? "(this Map)" : key2);
1237             buf.append('=');
1238             buf.append(value2 == this ? "(this Map)" : value2);
1239             buf.append(CollectionUtils.COMMA);
1240         case 1:
1241             buf.append(key1 == this ? "(this Map)" : key1);
1242             buf.append('=');
1243             buf.append(value1 == this ? "(this Map)" : value1);
1244             break;
1245         // case 0: has already been dealt with
1246         default:
1247             throw new IllegalStateException("Invalid map index: " + size);
1248         }
1249         buf.append('}');
1250         return buf.toString();
1251     }
1252 
1253     /**
1254      * Gets the values view of the map.
1255      * Changes made to the view affect this map.
1256      * To simply iterate through the values, use {@link #mapIterator()}.
1257      *
1258      * @return the values view
1259      */
1260     @Override
1261     public Collection<V> values() {
1262         if (delegateMap != null) {
1263             return delegateMap.values();
1264         }
1265         return new Values<>(this);
1266     }
1267 
1268     /**
1269      * Serializes this object to an ObjectOutputStream.
1270      *
1271      * @param out the target ObjectOutputStream.
1272      * @throws IOException thrown when an I/O errors occur writing to the target stream.
1273      */
1274     private void writeObject(final ObjectOutputStream out) throws IOException {
1275         out.defaultWriteObject();
1276         out.writeInt(size());
1277         for (final MapIterator<?, ?> it = mapIterator(); it.hasNext();) {
1278             out.writeObject(it.next());  // key
1279             out.writeObject(it.getValue());  // value
1280         }
1281     }
1282 
1283 }