首先梳理一下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://blog.csdn.net/wangjun5159/article/details/61415263
https://blog.csdn.net/bimuyulaila/article/details/52088124
https://blog.csdn.net/yueaini10000/article/details/78933289