java.util.HashMap
类继承了 java.util.AbstractMap
抽象类,实现了 java.util.Map
、 java.lang.Cloneable
、 java.io.Serializable
接口。
一、HashMap特点或规范 二、成员属性 三、构造器 四、内部类:Node,即:Map.Entry 五、继承自AbstractMap的方法 六、实现自Map接口的方法 七、静态工具方法 八、其他主要方法 九、迭代器 十、红 黑 树
HashMap 是基于 哈希表 的 Map 接口实现。无序 且不保证顺序永远保持不变。
1.1 与Hashtable的区别
HashMap
Hashtable
不同步
同步
允许空键
不允许空键
允许空值
不允许空值
1.2 性能
通常情况下,**hash(Object)
** 方法计算得出的哈希值都均匀的分布在 哈希桶 之间,这样可以保证 get(K)
和 put(K, V)
基本操作方法的性能为恒定时间。
对集合视图:**keySet()
, values()
, entrySet()
** 的 迭代时间 ,与 HashMap实例的容量(桶的数量)加上其大小(K-V Node 的数量)成比例的时间。因此,如果迭代性能有较高要求,则不要将 初始容量设置得太高 或 负载因子设置得太低。
影响 HashMap 性能的两个因素:初始容量 和 负载因子 。
1.3 容量capacity & 加载因子loadFactor
容量 :哈希表中的桶数,初始容量只是创建 HashMap 时的容量。
加载因子 :是一个比例值,即:已被分布的哈希桶数 / 容量;也可以描述为:扩容操作之前允许哈希桶中已被分布的桶的数量 。
当 HashMap 中已被分布的桶数超过了 加载因子 * 当前容量 时,HashMap将被扩容至原来容量的两倍,并重新计算哈希值(即,重建内部数据结构)。
默认加载因子为:0.75 ,这个值在时间和空间成本之间提供了良好的权衡。 较高的值会减少空间开销,但会增加查找成本。在设置初始容量时,应考虑 Map 中的预期 Entry 数及加载因子,以避免多次扩容带来的性能损耗。 如果要将一个包含多个 Entry 的 Map 存储在 HashMap 中,应使用足够大的初始容量来创建,否则 HashMap 自身的扩容机制会多次进行扩容,严重影响性能。
1.4 HashMap的数据结构 1.4.1 hash算法 HashMap 是通过对 key 进行 hash() 计算得出该节点(Node) 在 table 中的位置。详情:hash(Object)方法 。
1.4.2 hash冲突及常见解决办法 当 HashMap 中节点过多时,不可避免的会出现 hash 冲突的问题,常见解决办法有两类:
开放定址法 :又有两种:
线性探测 :当 hash 冲突时,查找下一个位置是否被占用,如果被占用则继续查找下一个位置,直到找到空位置。
平方探测 :以平方大小查找下一个位置,假如:在2位置处发生冲突,则探测 (1^2)+1=3 位置处,如果3位置处也被占用,则探测 (2^2)+2=6 位置处,(3^2)+2=11 …
缺点:虽然效率有所提升,但会浪费空间;在哈希表未满的情况下,可能无法继续放入新元素。
链地址法 :HashMap 就采用了这种方式。
这种方式的哈希表有一个桶 的概念,即:哈希桶 ,每一个链表就是一个哈希桶,链表中存放节点 Node。当发生 hash 冲突时,放入当前链表的末尾。
1.4.3 红 黑树 当哈希表某一位置处的链表达到 8 个以上,并且哈希表的长度大于 64,该位置的链表会转换为一种 平衡二叉树 :红黑树 。
1.5 HashMap不同步 如果多个线程并发访问 HashMap ,并且至少有一个线程在结构上进行了修改,则必须在外部进行同步。结构修改 是指 添加 或 删除 一个或多个 Entry
的任何操作,仅修改 Entry
的值不是结构修改。这通常通过同步自然封装映射的某个对象来完成。
正确的同步方式:
Map m = Collections.synchronizedMap(new HashMap(...));
1.6 fail-fast快速失败机制 HashMap 的所有“集合视图方法”返回的迭代器都是 fail-fast 的:如果在创建迭代器之后的任何时候对 Map 进行结构修改,除了通过迭代器自己的 remove()
方法之外,迭代器将抛出 ConcurrentModificationException
异常。因此,在并发修改的情况下,迭代器可快速地失败。
默认初始容量,必须是2的幂。
1 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ;
最大容量,2的30次幂。
1 static final int MAXIMUM_CAPACITY = 1 << 30 ;
默认加载因子:0.75f。
1 static final float DEFAULT_LOAD_FACTOR = 0.75f ;
链表中 Entry 数大于8时,将转为 红 黑树 结构。
1 static final int TREEIFY_THRESHOLD = 8 ;
红 黑树 转化为链表的长度临界值
1 static final int UNTREEIFY_THRESHOLD = 6 ;
转化 红 黑树 的最小数组长度为64,数组长度太小不会转化红黑树。
1 static final int MIN_TREEIFY_CAPACITY = 64 ;
HashMap 的基础数组结构,存放 Node 根节点。
1 transient Node<K,V>[] table;
存放 HashMap 中全部 Entry 的 Set 集合
1 transient Set<Map.Entry<K,V>> entrySet;
大小:HashMap 的 Entry 数量
被修改或删除的总次数
阈,临界值,当 HashMap 的大小达到临界值时,需要扩容
加载因子
3.1 遵循Map接口的构造器规范 java.util.Map
接口的构造器规范,提供一个 无参构造器 ,一个 参数类型为 Map 的构造器。初始加载因子为默认值:0.75,其余值都为默认值。
1 2 3 4 5 6 7 8 public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; }public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); }
3.2 HashMap自身构造器 支持指定 初始容量 ,加载因子为默认值:0.75
1 2 3 public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); }
支持指定 初始容量 及 加载因子 。
1 2 3 4 5 6 7 8 9 10 11 12 13 public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException ("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException ("Illegal load factor: " + loadFactor); this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static class Node <K,V> implements Map .Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this .hash = hash; this .key = key; this .value = value; this .next = next; } public final K getKey () { return key; } public final V getValue () { return value; } public final String toString () { return key + "=" + value; } public final int hashCode () { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue (V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals (Object o) { if (o == this ) { return true ; } if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) { return true ; } } return false ; } }
5.1 基础操作 对 HashMap 的基础操作,依赖于 hash() / getNode() / putVal() / putMapEntries() / removeNode()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public int size () { return size; }public boolean isEmpty () { return size == 0 ; }public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }public boolean containsKey (Object key) { return getNode(hash(key), key) != null ; }public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }public void putAll (Map<? extends K, ? extends V> m) { putMapEntries(m, true ); }public V remove (Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null , false , true )) == null ? null : e.value; }public void clear () { Node<K,V>[] tab; modCount++; if ((tab = table) != null && size > 0 ) { size = 0 ; for (int i = 0 ; i < tab.length; ++i) tab[i] = null ; } }public boolean containsValue (Object value) { Node<K,V>[] tab; V v; if ((tab = table) != null && size > 0 ) { for (int i = 0 ; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null ; e = e.next) { if ((v = e.value) == value || (value != null && value.equals(v))) return true ; } } } return false ; }@Override public Object clone () { HashMap<K,V> result; try { result = (HashMap<K,V>)super .clone(); } catch (CloneNotSupportedException e) { throw new InternalError (e); } result.reinitialize(); result.putMapEntries(this , false ); return result; }
5.2 转换为单列集合视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Set<K> keySet () { Set<K> ks; return (ks = keySet) == null ? (keySet = new KeySet ()) : ks; }public Collection<V> values () { Collection<V> vs; return (vs = values) == null ? (values = new Values ()) : vs; }public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet ()) : es; }
6.1 覆盖扩展自 JDK8 Map 的默认方法。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public V getOrDefault (Object key, V defaultValue) {}public V putIfAbsent (K key, V value) {}public boolean remove (Object key, Object value) {}public boolean replace (K key, V oldValue, V newValue) {}public V replace (K key, V value) {}public void forEach (BiConsumer<? super K, ? super V> action) {}public void replaceAll (BiFunction<? super K, ? super V, ? extends V> function) {}
lambda表达式计算操作的方法
具体说明以下方法的作用,测试数据:
1 2 3 4 Map<Integer, String> map = new HashMap <>(); map.put(1 , "a" ); map.put(2 , "b" ); map.put(3 , "c" );
computeIfAbsent() 如果 key 存在,则不进行计算,输出旧值。 如果 key 不存在,否则根据 key 计算新值,设置并返回。
1 2 3 public V computeIfAbsent (K key, Function<? super K, ? extends V> mappingFunction) {} map.computeIfAbsent(3 , key -> key.toString()); map.computeIfAbsent(4 , key -> key.toString());
computeIfPresent() 如果 key 存在,则根据 key/value 计算新的值,设置并返回,如果计算结果为 null,则删除 Node。 如果 key 不存在,不进行计算。
1 2 3 4 public V computeIfPresent (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} map.computeIfPresent(3 , (key, value) -> key + value); map.computeIfPresent(4 , (key, value) -> key + value); map.computeIfPresent(2 , (key, value) -> null );
compute() compute()
方法是 computeIfAbsent()
和 computeIfPresent()
方法的合体。 无论 key 是否存在,都根据 key/value 计算新值,如果计算结果为 null,则删除 Node。
1 2 3 4 public V compute (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} map.compute(3 , (k, v) -> k + v); map.compute(4 , (k, v) -> k + v); map.compute(2 , (k, v) -> null );
merge() 无论 key 是否存在,都根据 旧值 与 新值 进行计算,设置并返回计算结果,如果计算结果为 null,则删除 Node。
1 2 3 4 public V merge (K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {} map.merge(3 , "ccc" , (oldV, newV) -> oldV + newV); map.merge(4 , "ddd" , (oldV, newV) -> oldV + newV); map.merge(2 , "bbb" , (oldV, newV) -> null );
对 key 计算 hashCode 计算,int 类型的 hashCode 占 32 位,**h >>> 16
** 将 hashCode 右移16位,再与 hashCode 进行异或运算。
1 2 3 4 5 6 7 8 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } n = tab.length tab[(n - 1 ) & hash]
原因 :高16位与低16位进行异或运算,可以将高位上的变化体现到低位上,以此来避免 hash 碰撞,同时又保证了性能。**getNode()
** 方法根据 table 的最后一个索引值 与 hash 进行 与运算 ,将结果作为数组的索引进行取值。
1 2 3 4 5 6 7 8 9 10 0000 0000 0000 0000 0000 0000 0001 0000 1111 1111 1111 1111 0000 1010 0011 1010 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 0101 1100 0101 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 0101 1100 0101 0000 0000 0000 0000 0000 0000 0000 0101
只有最低的4位参与了计算,该 key 在 table 的 tab[5] 位置 。
7.2 tableSizeFor(int) 方法 此方法保证返回值是大于等于给定参数 cap 最小的2的幂次方的数值。具体算法是分别进行多次 右移 ,每次进行 或等运算 。
1 2 3 4 5 6 7 8 9 static final int tableSizeFor (int cap) { int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; return (n < 0 ) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ; }
假如,给定的初始化容量是10:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 0000 1010 0000 1001 0000 1001 0000 0100 0000 1101 0000 1101 0000 0011 0000 1111 0000 1111 0000 0000 0000 1111 0000 1111 0000 0000 0000 1111 0000 1111 0000 0000 0000 1111 0001 0000
putMapEntries(Map, boolean) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 final void putMapEntries (Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0 ) { if (table == null ) { float ft = ((float )s / loadFactor) + 1.0F ; int t = ((ft < (float )MAXIMUM_CAPACITY) ? (int )ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) { resize(); } for (Map.Entry<? extends K , ? extends V > e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false , evict); } } }
getNode(int, Object) 执行过程分析:
根据 (tab.length - 1) & hash
计算出元素在 table 中的索引位置,优先判断第一个:
如果第一个是要找的元素,返回该元素。
如果第一个不是要找的元素,则判断该节点是链表结构,还是树型结构:
如果是树型结构,根据 hash、key查找。
如果是链表结构,循环迭代 netx 节点,直到找到该元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final Node<K,V> getNode (int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1 ) & hash]) != null ) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null ) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null ); } } return null ; }
putVal(int, K, V, boolean, boolean) 方法 执行过程分析:
先判断 table 是否为空,如果为空,进行扩容。
根据 (tab.length - 1) & hash
计算出元素在 table 中的索引,并判断是否为空:
第一个节点为空,说明该 hash 桶下没有任何节点,直接放入即可。
第一个节点不为空:
当前节点与该位置的第一个节点的 hash 值一致,key 也相同,已存在。
判断该位置的结构:
树型结构:调用 putTreeVal()
放入。
链表结构:将该节点放入第一个节点的 next 位置,并检查是否需要转化为树型结构。
自增修改次数、size 大小,并判断 size 是否大于当前临界值,即:是否需要扩容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0 ) n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) { resize(); } afterNodeInsertion(evict); return null ; }
resize() 方法 执行过程分析:
先判断旧容量是否大于 0:
如果旧容量大于 0,说明 table 已初始化,继续判断:
如果旧容量大于等于最大容量限制,将临界值 threshold
设置为 **Integer.MAX_VALUE
**。
如果将容量扩容 2 倍后小于最大容量限制,且旧容量大于等于初始容量 16,则扩容旧临界值为原来的 2 倍。
如果旧容量不大于 0,说明 table 未初始化,继续判断:
如果旧临界值大于 0,将旧临界值赋值给新容量,HashMap 初始化时,临界值为16。
否则就初始化默认参数。
再次校验新临界值是否为 0,如果为 0,初始化临界值 。
创建一个新 Node 数组,并循环复制全部节点:
如果循环中的当前位置只有一个节点,则直接赋值。
如果循环中的当前位置是红黑树,调用 split()
方法。
如果循环中的当前位置是链表,则复制并重新计算 hash 值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1 ; } else if (oldThr > 0 ) newCap = oldThr; else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0 ) { float ft = (float )newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ? (int )ft : Integer.MAX_VALUE); } threshold = newThr; Node<K,V>[] newTab = (Node<K,V>[])new Node [newCap]; table = newTab; if (oldTab != null ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) newTab[e.hash & (newCap - 1 )] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this , newTab, j, oldCap); else { Node<K,V> loHead = null , loTail = null ; Node<K,V> hiHead = null , hiTail = null ; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0 ) { if (loTail == null ) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null ) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
removeNode(int, Object, Object, boolean, boolean) 方法 核心删除方法,其他 remove 方法都依赖于此方法。执行过程分析:
首先执行的前提是 table 已初始化,且不为空。
根据 hash 值计算出的位置,确定该位置处是一个节点,红黑树还是链表,并查找。
删除,判断该位置的类型,执行不同的删除方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 final Node<K,V> removeNode (int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1 ) & hash]) != null ) { Node<K,V> node = null , e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null ) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break ; } p = e; } while ((e = e.next) != null ); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this , tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null ; }
keySet()
, values()
, entrySet()
三个视图方法中的迭代器都为 HashIterator
抽象类的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 abstract class HashIterator { Node<K,V> next; Node<K,V> current; int expectedModCount; int index; HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null ; index = 0 ; if (t != null && size > 0 ) { do {} while (index < t.length && (next = t[index++]) == null ); } } public final boolean hasNext () { return next != null ; } final Node<K,V> nextNode () { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException (); if (e == null ) throw new NoSuchElementException (); if ((next = (current = e).next) == null && (t = table) != null ) { do {} while (index < t.length && (next = t[index++]) == null ); } return e; } public final void remove () { Node<K,V> p = current; if (p == null ) throw new IllegalStateException (); if (modCount != expectedModCount) throw new ConcurrentModificationException (); current = null ; K key = p.key; removeNode(hash(key), key, null , false , false ); expectedModCount = modCount; } }
1 2 3 4 5 6 7 8 9 10 11 12 final class KeyIterator extends HashIterator implements Iterator <K> { public final K next () { return nextNode().key; } }final class ValueIterator extends HashIterator implements Iterator <V> { public final V next () { return nextNode().value; } }final class EntryIterator extends HashIterator implements Iterator <Map.Entry<K,V>> { public final Map.Entry<K,V> next () { return nextNode(); } }