栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

hashMap中entrySet()源码解析 迭代器的使用 源码快速迭代的实现方法

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

hashMap中entrySet()源码解析 迭代器的使用 源码快速迭代的实现方法

一直搞不明白hashMap中entrySet()方法是如何将map转为Set的 由于好奇查看了一下源码结果一发不可收拾可能是我技术不精看的很模糊 最后偶然debugentrySet.toString 才大概明白

先说总结

  1. Map转Set的原理就是将 k : v 转为 “ k = v ” 保存的
  2. 主要通过迭代器来完成的 每个map数据保存的容器是Node
  3. 能生成的Set只是因为多态继承了Set 实际上是要使用的时候 是通过EntryIterator迭代器来遍历集合 并通过一定手段来转为为字符串的输出的
    看一下测试用例
@Test
public void test(){
    Map map = new HashMap();
    map.put(1, "张三A");
    map.put(2, "李四");
    map.put(3, "王五");
    Set> entries = map.entrySet(); //这里用 Entry 是Map接口中的一个内部		接口,在这里实际上使用的是Node 类 采用了多态思想 
    System.out.println(entries。toString());
}

现在我们来看看源码是怎么写的

1.在执行方法的时候会先查看有没有EntrySet类 如果有就使用 没有则新 new 一个 EntrySet

public Set> entrySet() {
    Set> es;
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}

那么EntrySet有何用处呢? EntrySet类代码比较多就不全放出来了 其主要实现了一些 操作方法 size() ,clear(),contains(Object o),remove(Object o),iterator()迭代器等 我们主要说iterator()

final class EntrySet extends AbstractSet> {
    	//注意 AbstractSet 继承于AbstractCollection
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator> iterator() {
            return new EntryIterator();
            //其迭代器继承关系
  			//final class EntryIterator extends HashIterator implements //Iterator>  {
        //	public final K next() { return nextNode(); }  返回下个数据
    	  	
        }
}

HashIterator 创建了一个快速失败迭代器 (Node是一个内部类用于保存map数据的 下面也有源码)

abstract class HashIterator {
    Node next;        // 下一个返回的条目
    Node current;     // 当前的条目
    int expectedModCount;  // 用于快速迭代中期待的正确值 
    int index;             // 当前条数

    HashIterator() {
        expectedModCount = modCount; //modCount 是hashmap类中定义的一个用于保存修改次数的值
        Node[] 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 nextNode() {
        Node[] t;
        Node e = next;
        //作用是判断在迭代期间有没有修改数据 以快速失败 如果修改了数据modCount就会自增 下面会列出put的源码
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
         
        //(next = (current = e).next) 我们主要看这个
        
        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 p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        removeNode(p.hash, p.key, null, false, false);
        expectedModCount = modCount;
    }
}

附上Node类 的源码 实现 将 k : v 转为 “ k = v ” 的原理就在这

static class Node implements Map.Entry {
    final int hash;
    final K key;
    V value;
    Node next;

    Node(int hash, K key, V value, Node next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
	
    
    public final K getKey()        { return key; }  
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }  
 }

附上 put的源码 modCount自增的行数有标记

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node[] tab; Node p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode)p).putTreeval(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;/
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

好了到这部分我们就知道 是如何实现迭代的了 但是为什么 会将Map转成Set我们任然没有解决 我们继续,其实这个的原理真的很简单

System.out.println(entries.toString());

对toString进行debug发现进入了AbstractCollection 类中 为什么会进入这个类呢?因为

EntrySet extends AbstractSet>

而AbstractSet又继承了AbstractCollection

AbstractCollection 重写了toString

public String toString() {
    Iterator it = iterator(); //这里new 的就是 EntrySet 下的 iterator()可以在回去看看 
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
		 
        sb.append(e == this ? "(this Collection)" : e); //调用每个Node中的toString()
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

之后我就看了一下

Set set = map.keySet(); // 得到全部的key
Collection value = map.values(); // 得到全部的value

上面的keySet() values()实现方式基本和entrySet()的实现方式一样 嘿嘿 ! 一个小惊喜 这波买卖不亏

好了 上面的代码就比较简单不多说了 在这里还有一个小插曲 就是用完toString()想着直接打印一下entries 发现调用的println中的

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newline();
    }
}

之后调用valueOf()

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

又去调用该对象toString()方法 这也解开了我好久的疑惑 为什么不用toString 打印效果也和使用toString()一样了,这可是意外之喜呢!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/305436.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号