我所理解的JDK集合類(二):從源碼分析HashMap的遍歷方式

上一篇文章簡單的寫過了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內部的EntryIteratorKeyIteratorValueIterator都繼承自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()就挺好。

相關文章
相關標籤/搜索