HashMap中keySet和entrySet的区别

今天进行CR的时候,发现代码中对HashMap进行遍历时,使用的是for(key in map.keys),就想起了之前看到的一个文章,说使用entrySet()进行遍历性能更高。当时没有追究,但是最近看《代码整洁之道》,学到了

勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)

那么今天一定要把其中的原理搞清楚。

用法

  • keySet()
for (key in map.keys) {
  map[key]
}
  • entrySet()
for (entry in map.entries) {
  entry.value
}

差异

  • keySet()返回的是一个关于K的Set (第一次查询),要获取value,还需要通过map[key]获取 (第二步查询)。

  • entrySet()返回的是一个K,V键值对的Set (第一次查询),获取value直接调用entry.value (不需要查询)。

思考

  1. 虽然使用entrySet()进行遍历,性能更好,但是多线程下就没办法保证安全性了,所以在多线程情况下,建议考虑使用ConcurrentHashMap()

  2. 观察entrySet()方法,发现entrySetnull时,返回的是EntrySet()对象,但是看EntrySet源码,构造方法是空实现,那数据从哪里来的呢?先看下源码:

public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    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(); }
    // 这里返回了EntryIterator()
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
    public final boolean contains(Object o) {
        if (!(o instanceof Map.Entry<?, ?> e))
            return false;
        Object key = e.getKey();
        Node<K,V> candidate = getNode(key);
        return candidate != null && candidate.equals(e);
    }
    public final boolean remove(Object o) {
        if (o instanceof Map.Entry<?, ?> e) {
            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 (Node<K,V> e : tab) {
                for (; e != null; e = e.next)
                    action.accept(e);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

可以发现iterator()方法返回了EntryIterator()对象,深入其中,发现其继承了HashIterator,而HashIterator构造方法则是从关联到了table,在HashMapput方法中,就是向table中插入值。

可能有人会问,这与EntrySet()有什么关系呢?for循环怎么获取到的Set对象呢?
通过对kotlin代码转成Bytecode,再反编译成java语言,可以发现:
var3 = map.entrySet().iterator();

while(var3.hasNext()) {
   Map.Entry entry = (Map.Entry)var3.next();
   entry.getValue();
}

就是说for (entry in map.entries),实际上是遍历的迭代器,这就与前面EntrySetiterator()方法的返回值关联起来了,突然想起了名侦探柯南的画面,哈哈哈

作者:萌萌的老曹
链接:https://juejin.cn/post/7174818136456331321

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值