上一篇文章簡單的寫過了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()就挺好。