各位看官,大家早安午安晚安呀~~~
如果您觉得这篇文章对您有帮助的话
欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦
今天我们来学习:Map的遍历方式(源码分析 + 迭代器和CAS思想的关系)
目录
迭代器的工作流程:(本质就是把一个容器的元素,全部放到迭代器里面)
再开始之前,对于Map有困惑的小伙伴可以去看博主关于Map的讲解
这里面有一个很重要的点,安全的删除(remove)操作涉及迭代器(HashIterator)的快速失败机制
迭代器的安全删除,两个关键变量
1.expectedModCount
和modCount
的设计与 CAS(Compare-And-Swap)理论 有相似之处,它们都基于 乐观锁 的思想。
当通过迭代器的
remove()
方法删除元素时,迭代器会同步更新expectedModCount
(将modCount
重新赋值给expectedModCount
),确保后续操作不会触发异常。例如:
iterator.remove(); // 内部会执行 expectedModCount = modCount;
2.可以说,迭代器的快速失败机制是简化版的 CAS,它放弃了修复冲突的能力(因为集合结构修改后难以安全恢复),但保留了冲突检测的核心思想。
Map的遍历推荐方式:
推荐1.直接foreach方法
map.forEach((s,s1) ->{ System.out.println("key: "+ s + " , value" + s1); });推荐2:隐式迭代器
for(Map.Entry<String,String> entry: map.entrySet()){ System.out.println("key :" + entry.getKey() + ",value: "+entry.getValue()); //TODO,这里不能remove不然会报错呢 //map.remove(entry.getKey(),entry.getValue()); }推荐3:需要删除元素时候(显式迭代器)(下面会讲解)
推荐四:流(下面会讲解)
1.源码提供的,我们要重写BiConsume 的方法:
K,V都提供了。我们直接处理每一组KV即可
重写=》》》(三种写法)
方法引用的写法需要写一个额外的方法:sout(key + value)这样的方法不存在(不要忘了::的前提是一个使用一个已经存在的方法)
public static void printKV(String key, String value) {
System.out.println("Key: " + key + ", Value: " + value);
}
2.迭代器(安全remove???)
2.1.显示迭代器(安全remove)
迭代器的工作流程:(本质就是把一个容器的元素,全部放到迭代器里面)
- 获取迭代器:通过
map.entrySet().iterator()
或map.keySet().iterator()
等方法获取迭代器对象。- 遍历元素:通过
hasNext()
和next()
方法逐个访问元素。- 安全修改:在遍历过程中,可以通过
iterator.remove()
安全地删除元素。
不可以使用 map.remove(entry);
2.2.直接foreach循环
看一下代码:
结果 =》》》
2.3. 本质区别
为什么迭代器可以安全删除元素?
ransient
是一个关键字,用于修饰类的成员变量。当一个变量被声明为 transient
时,它的核心作用是:在对象序列化时,忽略该变量,不将其状态保存到序列化流中。
expectedModCount
:迭代器内部维护的预期修改计数,初始化时与modCount
同步modCount(Map内部维护的修改计数器,每次增删元素时自增)
1.expectedModCount
和modCount
的设计与 CAS(Compare-And-Swap)理论 有相似之处,它们都基于 乐观锁 的思想。2.可以说,迭代器的快速失败机制是简化版的 CAS,它放弃了修复冲突的能力(因为集合结构修改后难以安全恢复),但保留了冲突检测的核心思想。
这涉及到 HashMap 的 快速失败(Fail-Fast)机制:
搜源码的时候可以 直接搜 HashIterator 这个抽象类下面的remove方法
关键变量:
modCount
:HashMap
内部维护的修改计数器,每次增删元素时自增。expectedModCount
:迭代器内部维护的预期修改计数,初始化时与modCount
同步。
安全删除的原理:
调用
iterator.remove()
:
- 删除当前元素。(本质上是调用map的remove,然后更新expectModCount使得和 ModCount相等)
- 更新
modCount
和expectedModCount
,确保两者一致。直接调用
map.remove(key)
: 这个时候仅更新modCount
,但expectedModCount
不变。下次调用iterator.next()
时,检测到modCount != expectedModCount
,抛出ConcurrentModificationException
。
3.keySet和values的遍历方式
4.流的方式:
并行流处理大量数据效率更高
// 串行流
map.entrySet().stream().forEach(entry -> {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
});
// 并行流(在处理大量数据时效率更高)
map.entrySet().parallelStream().forEach(entry -> {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
});
5.比较两种remove:
1.remove(key)最常用的删除方式
2.remove(key,value)
源码:
方法 | 参数类型 | 返回值 | 描述 |
---|---|---|---|
remove(Object key) |
键(Key) | 被删除的值(可能为null ) |
删除指定键对应的键值对,返回被删除的值。若键不存在,返回null 。 |
remove(Object key, Object value) |
键(Key)和值(Value) | 布尔值(是否删除成功) | 仅当键和值都匹配时才删除,返回是否删除成功。Java 8+ 新增。 |
- 日常删除优先使用
remove(key)
。- 需要验证值是否匹配时使用
remove(key, value)
。- 迭代过程中删除元素必须使用
iterator.remove()
。- 多线程环境下使用
ConcurrentHashMap
并调用其remove(key, value)
方法
最后--所有的代码博主双手奉上:
package com.example.demomvc.controller;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiConsumer;
public class Test {
// 定义一个静态方法
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("第一","111");
map.put("第二","222");
map.put("第三","333");
// map直接的遍历方式 ;匿名内部类,lamda,:: 都可以
// 1.1.第一种写法
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println("key :" +s +", value:"+s2);
}
});
// 1.2.第二种写法
map.forEach((s,s2) -> System.out.println("key :" +s +", value:"+s2));
// 1.3.第三种写法,第三种写法的注意事项
// map.forEach(System.out::println); 这种方法不行
// 方法引用只能引用已经存在了的方法,对于双参数还是要用lambda表达式,而不是方法引用。(或者我们再写一个方法)
// 你可能尝试过直接用 System.out::println,但它只能处理单个参数(Consumer)
map.forEach(Test::printKV);
//
// Iterator<Map.Entry<String, String>> iterator1 = map.entrySet().iterator();
//
// while(iterator1.hasNext()){
// Map.Entry<String, String> entry = iterator1.next();
// System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue());
// if (entry.getValue() == "111") {
// map.remove(entry); // 这里不可以,会报错
// }
// }
// 迭代器方式1 :生成一个迭代器容器,可以访问迭代器对象,然后使用迭代器对象的方法
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key:" + key + ", value:" + value);
// 若要在遍历过程中删除元素,可使用iterator.remove()
if (value == "111") { //删除
iterator.remove(); // 安全删除
}
}
System.out.println("================");
// 迭代器方式2, =>>> 这个时候就不会有aaa这个key了
for (Map.Entry<String,String> entry:map.entrySet()) {
System.out.println(entry.getKey());
}
//使用 keyset 方式
for(String key:map.keySet()){
System.out.println("key的值:" + key );
}
// 使用 values 迭代方式
for(String value:map.values()){
System.out.println("value的值:" + value );
}
}
public static void printKV(String key, String value) {
System.out.println("Key: " + key + ", Value: " + value);
}
}
需要查询源码的类
上述就是Map的遍历方式(源码分析 + 迭代器和CAS思想的关系)的全部内容啦,不知道您对文章中的问题和思想是否都学会理解了呢?
能看到这里相信您一定对小编的文章有了一定的认可。
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~