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