上一篇文章简单的写过了HashMap的工做原理。接下来从JDK1.8的源码的角度来分析一下HashMap的遍历方式。node
1,使用keySet遍历,使用map.get(key)获取value数组
public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; }
keySet()方法返回的是一个new KeySet()。而KeySet是HashMap的一个内部类函数
final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { // JDK1.8新增,方便Stream流式API操做 return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { // JDK1.8新增,方便lambda形式遍历 Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
这个KeySet继承自AbstractSet。AbstractSet定义了hashCode()和equals()方法,调用iterator()实现了removeAll()方法。AbstractSet继承了AbstractCollection,AbstractCollection定义了集合类操做的一些基本方法,好比toArray(),contains(e),remove(e)等等。 几乎全部的Set接口的实现类都是继承自AbstractSet。ui
而Set类的遍历方法,无非就是for循环、foreach、iterator三种。for循环须要的是size(),源码已有。foreach内部实现仍是调用了iterator的hasNext()和next()。而iterator()从源码上看返回的是HashMap的内部类KeyIterator。来分析一下KeyIterator的源码:this
final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } }
这里有引入了nextNode()方法。nextNode()是其父类HashIterator的方法。HashIterator是HashMap的一个内部类。HashMap内部的EntryIterator、KeyIterator、ValueIterator都继承自HashIterator。.net
abstract class HashIterator { Node<K,V> next; // 下一次要返回的节点 Node<K,V> current; // 当前已经返回的节点 int expectedModCount; // 检查符,参考上一边文章提到的ConcurrentModificationException int index; // 当前hash表数组的索引值 HashIterator() { expectedModCount = modCount; // 初始赋值,将HashMap的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) { // 肯定下一个返回的next节点 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); // 调用HashMap的removeNode()删除元素 expectedModCount = modCount; // removeNode()改变了modeCount的值再次赋给本身 } }
以上分析可见,使用keySet()方便返回keySet来进行遍历,实际上是经过调用iterator()返回的节点的key。
顺便看一下EntryIterator、ValueIterator的源码:code
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(); } }
可见,这三个的遍历其实都是使用了nextNode()也就是node节点遍历。blog
2,使用EntrySet遍历。使用entry.getKey()和entry.getValue()来获取key和value。
来看一下entrySet()的源码:继承
public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; }
这里引入了 new EntrySet()。EntrySet是HashMap的一个内部类。来看一下EntrySet()的源码。索引
final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { // JDK1.8新增,方便Stream流式API操做 return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { // JDK1.8新增,方便lambda表达式遍历 Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
经过对以前的KeySet的分析,这段EntrySet代码的关键部分在于iterator()中引入的new EntryIterator()。而在分析KeyIterator的最后面已经引入过了EntryIterator的源码。如此的简单:
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } }
接下来就剩惟一一个没有看到源码的了,Map.Entry。HashMap在内部类EntryIterator中定义了内部类Entry,实现了Map.Entry接口。先来看看Map.Entry的源码:
interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode(); // 还有JDK1.8新增的public static 的comparingByXxx的方法 }
这个Map.Entry接口的方法从方法名就知道是要干什么的。而EntryIterator中定义了内部类Entry只是实现了相关方法的具体实现而已。因为本文想阐述HashMap的遍历,就再也不分析EntryIterator中定义了内部类Entry的实现源码了。之后有机会的再单独介绍HashMap总体的原理吧。
3,使用values()。因为这种方式只能获得value,不能获得key。严格意义上不能算做HashMap的遍历。
经过对keySet()的分析,很容易就能理解values()的源码:
public Collection<V> values() { Collection<V> vs = values; if (vs == null) { vs = new Values(); values = vs; } return vs; }
Values是HashMap的内部类,源码:
final class Values extends AbstractCollection<V> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<V> iterator() { return new ValueIterator(); } public final boolean contains(Object o) { return containsValue(o); } public final Spliterator<V> spliterator() { return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
4,使用JDK1.8新增的Map.foreach()。源码:
default void forEach(BiConsumer<? super K, ? super V> action) { Objects.requireNonNull(action); for (Map.Entry<K, V> entry : entrySet()) { K k; V v; try { k = entry.getKey(); v = entry.getValue(); } catch(IllegalStateException ise) { // 这个异常一般意味着这个entry已不在当前的map中 throw new ConcurrentModificationException(ise); } action.accept(k, v); } }
这种形式的遍历从源码很明白的看出使用的是entrySet()进行遍历的。这种形式的遍历一般是配合lambda表达式或者Stream流操做进行函数式遍历。
结论:
至此,最根本的两种HashMap的遍历方式的源码都已经很清楚了,有关遍历的效率的结论也很明显:使用entrySet()后entry.getKey()和entry.getValue()是最有效率的遍历。若是只须要遍历key,那使用keySet()也可。若是只须要遍历value,那是用values()就挺好。