Java 集合中的一些问题

1.数组转集合

   1.使用 Arrays.asList()

public class Demo {
	public static void main(String[] args) {
		String[]  array = {"he","jianfe","sa"};
		List<String> list = Arrays.asList(array);
		list.add("qw");
	}
}

运行时候抛出异常:
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at Demo.main(Demo.java:14)
  • 注意:调用Arrays.asList()时,其返回值类型是ArrayList,但此ArrayListArray的内部类,调用add()时,会报错:java.lang.UnsupportedOperationException,并且结果会因为array的某个值的改变而改变,故需要再次构造一个新的ArrayList

   2.Collections.addAll()

2.普通for循环   与迭代器循环

   ArrayList底层是一个数组,支持随机访问,而且源码中也是继承了RandomAcess接口(只是一个标志,标志是否支持随机访问),所以用普通for循环,直接取下标取遍历对象,效率是非常高效的。 如果用迭代器取遍历,由于迭代器是按顺序遍历的,会失去ArrayList的随机访问优势,效率则不会高效。

LinedList底层是双向链表,不支持随机访问。如果用普通for循环遍历,那么过程是这样的:

1、get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据

2、get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据

3、get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据。

也就是get(n)的时候,会把前面的n-1个都遍历一遍。效率可想而知。

结论   ArrayList   使用普通for循环遍历

          LinkedList  使用迭代器  或者增强for循环遍历

         如果遍历时候有增删   那么均要迭代器

         数据量小的时候各种遍历方式差距不明显,  数据量大时候相差甚远

3.迭代器

   迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。

4.快速失败 

    介绍描述:  java集合的一种错误检测机制,当多个线程对某个集合进行操作的时候,可能会对集合在结构上有所改变(比如数量的增,删),都有可能触发ConcurrentModificationException(并发修改异常)。

   异常复现:

public class Demo {
	public static void main(String[] args) {
        ArrayList<String> nameList = new ArrayList<>();
        nameList.add("hejianfeng");
        nameList.add("jiangtingyan");
        nameList.add("zhoujielun");
        
        for(String element : nameList) {
        	if(element.equals("hejianfeng")) {
        	   nameList.remove(element);
        	}
        }
	}

}


Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at Demo.main(Demo.java:11)

上面代码,我们用增强for循环对集合进行遍历,并在遍历的过程中对集合进行删除操作(也就是结构性的修改),可以看见触发了该异常。

原理讲解:

 结论是:

       在集合类中如ArrayList中有一个变量modCount, 初始化为0,源码中的一些涉及结构性修改的方法,如add(),remove()等都会修改该值。.

       迭代器在list.itertor(),随着集合创建出来的时候(注意:增强for循环其实是迭代器的语法糖),其中的exceptModCount会默认等于创建时候List的modCount. 

.      如果遍历的过程中  exceptModCount != modCount    ,那么就会抛出并发修改异常。  说明modCount变化了,集合结构被修改了。虽然是说并发修改异常,但是很明显,即使在单线程的操作下也是会如此的。

      简而言之:创建迭代器时候,modCount被记录(modCount == exceptedModCount),使用迭代器遍历时,modCount不能变,变了就说明有可能被修改 直接抛异常

如何避免:

    根据原理来看,不在迭代器遍历过程中让不modCount改变不就行了 或者 不适用迭代器  ,接下来正确的姿势

1.使用迭代器提供的remove方法,使用迭代器自己的remove方法她会同步expectedModCount的值,就不会抛异常

 2.java的stream API

3. 使用fail-safe的集合类

fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会出现并发修改异常

   

发布了29 篇原创文章 · 获赞 34 · 访问量 8135

猜你喜欢

转载自blog.csdn.net/weixin_39634532/article/details/103099224