您的位置:首页 > 其它

HashMap的四种遍历方式与for each循环原理以及for each循环增删操作异常原因

2020-01-15 11:08 316 查看

首先梳理一下List、Map、Set这三种常用的集合

  • List特点:元素有序,可重复
  • Set特点:元素无序,不可重复
  • Map特点:元素按键值对存储,无序

1.通过keySet遍历

Map<String,String> userMap = new HashMap<>();
Set<String> keySet = userMap.keySet();
for(String key : keySet){
System.out.println("key:"+key+"value:"+userMap.get(key));
}

简单好理解

2.通过Entry遍历

Map<String,String> userMap = new HashMap<>();
for(Map.Entry<String, String> m :  userMap.entrySet()){
System.out.println("key:"+m.getKey()+"value:"+m.getValue());
}

Map.Entry: HashMap内部用以存储元素的对象,使用匿名内部类实现,内部维护了getKey与getValue方法

entrySet():Map类提供的方法,这个方法返回一个Map.Entry实例化后的对象集

整体性能上最为优秀的一种遍历方式,推荐使用

3.通过迭代器iterator遍历

Map<String,String> userMap = new HashMap<>();
Iterator iter = userMap.keySet().iterator();//
while (iter.hasNext()) {
System.out.println("key:"+iter.next()+"value:"+userMap.get(iter.next()));
}

实质上即是将获取到的keySet转化为迭代器,再通过迭代器遍历,在性能上比直接用keySet稍好,同时可以在循环时操作集合内元素,这是其它遍历方法所不具备的。

4.通过Lambda表达式遍历

Map<String,String> userMap = new HashMap<>();
userMap.forEach((k,v) -> System.out.println("key:"+k+"value:"+v));//注意括号

API: forEach(BiConsumer<? super K,? super V> action)

这里使用了map中自带的forEach方法直接获取key与value,然后通过Lambda表达式完成输出

简洁明了,如果想装逼可以使用,性能方面并不如第二种方式

4.1:回顾一下Lambda表达式

expression = (variable) -> action
variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
action: 实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。
eg:int sum = (x, y) -> x + y;

5.for each循环遍历时删除元素抛出异常原理

当你尝试在for each中删除集合中一个元素时,如下

for(Map.Entry<String, String> m :  userMap.entrySet()){
System.out.println("key:"+m.getKey()+"value:"+m.getValue());
userMap.remove(m.getKey());
}

好像没什么问题,然而。。。

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
at factoryDemo.Demo.main(Demo.java:32)

使用iterator

Iterator iter = userMap.keySet().iterator();
while (iter.hasNext()) {
System.out.println("key:"+iter.next()+"value:"+userMap.get(iter.next()));
iter.remove();
}
莫得问题

为什么for each产生异常ConcurrentModificationException,而iterator不会,why?

实质上foreach循环其实就是根据集合对象创建一个iterator迭代对象,用这个迭代对象来遍历集合,相当于集合对象中元素的遍历托管给了iterator,如果要对集合进行增删操作,都必须经过iterator。

如下:一个正常的for each循环

List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
a.add("3");
for(String temp : a){
System.out.print(temp);
}

反编译之后:

List a = new ArrayList();
a.add("1");
a.add("2");
a.add("3");
String temp;
for(Iterator i$ = a.iterator(); i$.hasNext(); System.out.print(temp)){
temp = (String)i$.next();
}

很明显,for each在经过编译器之后,实质上为普通for循环,使用的是iterator去遍历集合,这也是为何通过iterator遍历集合的效率比直接使用KeySet效率更高。。那么问题来了,因为所有集合实现了Iterator接口,所以遍历时走的Iterator的方法,数组也可以用for each,那岂不是。。

没错:走的仍然是for(int i=0; i< len; i++)经典模式(正是在下!)咳咳,有兴趣的可以自己写一个数组遍历,然后反编译一下看看。

我们回归正题,当这里使用的iterator迭代器去遍历集合在生成iterator的时候,会保存一expectedModCount参数,这个是生成iterator的时候集合中元素的个数。如果你在遍历过程中删除元素,集合中modCount就会变化,如果这个modCount和exceptedModCount不一致,就会抛出异常。

//当我们在for each循环时删除或者增加元素时,就会使得modCount和exceptedModCount不一致,从而抛出异常,但是使用iterator.remove时为什么不出异常,查看HashMap中源代码
abstract class HashIterator :
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;//对modCount和exceptedModCount进行了处理
}

当然不仅是HashMap,其它的集合类也会出现这种问题,所以使用迭代器循环虽然效率不是最高,但也有它的优点

引用:
https://www.geek-share.com/detail/2700208120.html
https://www.geek-share.com/detail/2681048143.html
https://www.geek-share.com/detail/2725539976.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
Godwzl_X 发布了2 篇原创文章 · 获赞 0 · 访问量 142 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: