我所理解的JDK集合类(二):从源码分析HashMap的遍历方式

上一篇文章简单的写过了HashMap的工作原理。接下来从JDK1.8的源码的角度来分析一下HashMap的遍历方式。

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。

而Set类的遍历方法,无非就是for循环、foreach、iterator三种。for循环需要的是size(),源码已有。foreach内部实现还是调用了iterator的hasNext()和next()。而iterator()从源码上看返回的是HashMap的内部类KeyIterator。来分析一下KeyIterator的源码:

    final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }

这里有引入了nextNode()方法。nextNode()是其父类HashIterator的方法。HashIterator是HashMap的一个内部类。HashMap内部的EntryIteratorKeyIteratorValueIterator都继承自HashIterator。

    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的源码:

    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节点遍历。

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()就挺好。

转载于:https://my.oschina.net/u/3466682/blog/1575685

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值