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

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

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

1.通过keySet遍历

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

简单好理解

2.通过Entry遍历

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

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

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

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

3.通过迭代器iterator遍历

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

API: forEach(BiConsumer 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 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 a = new ArrayList();
        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 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

你可能感兴趣的:(基础练习)