公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(1)

简介: 公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!

作者:你呀不牛

链接:https://juejin.cn/post/7114669787870920734

前段时间,同事在代码中KW扫描的时候出现这样一条:

image.png


上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。


于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。


于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?


于是今天决定把这个HashMap遍历操作好好地研究一番,防止采坑!


foreach循环?

java foreach 语法是在jdk1.5时加入的新特性,主要是当作for语法的一个增强,那么它的底层到底是怎么实现的呢?下面我们来好好研究一下:


foreach 语法内部,对collection是用iterator迭代器来实现的,对数组是用下标遍历来实现。Java 5 及以上的编译器隐藏了基于iteration和数组下标遍历的内部实现。


(注意,这里说的是“Java编译器”或Java语言对其实现做了隐藏,而不是某段Java代码对其实现做了隐藏,也就是说,我们在任何一段JDK的Java代码中都找不到这里被隐藏的实现。这里的实现,隐藏在了Java 编译器中,查看一段foreach的Java代码编译成的字节码,从中揣测它到底是怎么实现的了)


我们写一个例子来研究一下:


public class HashMapIteratorDemo {
    String[] arr = {"aa", "bb", "cc"};
    public void test1() {
        for(String str : arr) {
        }
    }
}



将上面的例子转为字节码反编译一下(主函数部分):


image.png


也许我们不能很清楚这些指令到底有什么作用,但是我们可以对比一下下面段代码产生的字节码指令:


public class HashMapIteratorDemo2 {
    String[] arr = {"aa", "bb", "cc"};
    public void test1() {
        for(int i = 0; i < arr.length; i++) {
            String str = arr[i];
        }
    }
}


image.png


看看两个字节码文件,有木有发现指令几乎相同,如果还有疑问我们再看看对集合的foreach操作:


通过foreach遍历集合:


public class HashMapIteratorDemo3 {
    List<Integer> list = new ArrayList<>();
    public void test1() {
        list.add(1);
        list.add(2);
        list.add(3);
        for(Integer var : list) {
        }
    }
}


通过Iterator遍历集合:


public class HashMapIteratorDemo4 {
    List<Integer> list = new ArrayList<>();
    public void test1() {
        list.add(1);
        list.add(2);
        list.add(3);
        Iterator<Integer> it = list.iterator();
        while(it.hasNext()) {
            Integer var = it.next();
        }
    }
}


将两个方法的字节码对比如下:


image.png



image.png




我们发现两个方法字节码指令操作几乎一模一样;


这样我们可以得出以下结论:


对集合来说,由于集合都实现了Iterator迭代器,foreach语法最终被编译器转为了对Iterator.next()的调用;


对于数组来说,就是转化为对数组中的每一个元素的循环引用。



相关文章
|
安全 Java API
java中HashMap的七种遍历方式
java.util.ConcurrentModificationException , 这种办法是非安全的 , 我们可以使用Iterator.remove() ,或者是Lambda 中的 removeIf() , 或者是Stream 中的 filter() 过滤或者删除相关数据
111 1
|
6月前
|
Java API
面试官上来就让手撕HashMap的7种遍历方式,当场愣住,最后只写出了3种
面试官上来就让手撕HashMap的7种遍历方式,当场愣住,最后只写出了3种
40 1
|
存储 算法 安全
HashMap的遍历方式及底层原理
HashMap的遍历方式及底层原理
|
Java API
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(2)
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!
194 0
公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!(2)
遍历HashMap的四种方式
遍历HashMap的四种方式
81 0
|
JavaScript 小程序 Java
HashMap 为什么不能一边遍历一遍删除
HashMap 为什么不能一边遍历一遍删除
|
1月前
|
Java
让星星⭐月亮告诉你,HashMap中保证红黑树根节点一定是对应链表头节点moveRootToFront()方法源码解读
当红黑树的根节点不是其对应链表的头节点时,通过调整指针的方式将其移动至链表头部。具体步骤包括:从链表中移除根节点,更新根节点及其前后节点的指针,确保根节点成为新的头节点,并保持链表结构的完整性。此过程在Java的`HashMap$TreeNode.moveRootToFront()`方法中实现,确保了高效的数据访问与管理。
29 2
|
1月前
|
Java 索引
让星星⭐月亮告诉你,HashMap之往红黑树添加元素-putTreeVal方法源码解读
本文详细解析了Java `HashMap` 中 `putTreeVal` 方法的源码,该方法用于在红黑树中添加元素。当数组索引位置已存在红黑树类型的元素时,会调用此方法。具体步骤包括:从根节点开始遍历红黑树,找到合适位置插入新元素,调整节点指针,保持红黑树平衡,并确保根节点是链表头节点。通过源码解析,帮助读者深入理解 `HashMap` 的内部实现机制。
32 2
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0