HashMap源码分析(三)

迭代&遍历

HashMap总共有3种遍历容器的方式:

1.使用Iterator迭代(推荐)

Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

2.将Key和Value分别转成Set和Collection遍历取值

for (String key : hashMap.keySet()){
    System.out.println(key);
}
for (String value : hashMap.values()){
    System.out.println(value);
}

3.get方式(一般不建议使用)

 for (String key : hashMap.keySet()){
     System.out.println(hashMap.get(key));
 }

这里,我们主要讲使用Iterator迭代器方式,从上面的例子中可以看出,获取迭代器之前要先调用entrySet(),当然keySet()、values()同样可以拿到迭代器,我们只要把entrySet()的方式讲明白了,其他的也是一样的,举一反三嘛,言归正传,接下来开始正式讲解HashMap的迭代原理和源码分析。

让我从一段代码开始讲起,看如下代码:

HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("aaa","aaa_v");
hashMap.put("bbb","bbb_v");
hashMap.put("ccc","ccc_v");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
System.out.println(entries);

从上面代码的执行结果可以得到结果:[aaa=aaa_v, ccc=ccc_v, bbb=bbb_v],另外,当我们在idea/eclipse中debug代码时,entries也是有值的,但是从源码看,entrySet()方法并没有做任何操作,entries集合中的值怎么来的,我们表示怀疑,或者说entries是不是真的能存储值,接下来慢慢揭开它的面纱

public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    //new EntrySet() 创建EntrySet,但是看EntrySet类并没有对应的构造方法,就算追溯到它的父类 AbstractSet<E> 和 AbstractCollection 里面,也只是一个空的构造方法,这就使EntrySet中有值这个事情不成立,那么EntrySet的值到底哪里来的?接着往下看
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
//没有构造方法
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() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super Map.Entry<K,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);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
    /**
     * Sole constructor.  (For invocation by subclass constructors, typically
     * implicit.)
     */
    protected AbstractSet() {
    }
}

其实当我们在debug或System.out.println 打印集合时,会主动调用集合的toString()方法,这就找到突破口了,从EntrySet->AbstractSet->AbstractCollection一路往上找,只有AbstractCollection类中有toString()方法,并且这个方法调用了一个iterator(),这个方法就是入口了
在这里插入图片描述
接下来回到HashMpa.EntrySet类中该方法的实现
在这里插入图片描述
EntryIterator类代码如下,并没有构造方法,继续往上找到HashIterator
在这里插入图片描述
HashIterator类

abstract class HashIterator {
    Node<K,V> next;        // next entry to return
    Node<K,V> current;     // current entry
    int expectedModCount;  // for fast-fail
    int index;             // current slot
    //构造方法
    HashIterator() {
        //默认expectedModCount 和 modCount两者相同,这是为了使容器在被迭代时其他线程不要让容器的结构有所变化,只结构变了modeCount就会改变,也就不会等于expectedModCount了,就会抛出并发异常
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { // 遍历到第1个有效的数组元素
            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;
    }
}

经过以上脉络分析,相信大家对entrySet()和keySet()为什么能拿到值就不奇怪了,实际这俩本身都没有值,只是在toString()方法的时候,通过调用iterator()迭代器来遍历值并输出,也就说从始至终,这两个Set都是空的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值