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