,在实际的业务开发中,我们通常会进行数组转List的操作,通常我们会使用Arrays.asList来进行转换
,但是在转换基本类型的数组的时候,却出现转换的结果和我们想象的不一致。
,上代码:,实际上,我们想要转成的List应该是有三个对象而现在只有一个:,可以观察到 asList方法 接收的是一个泛型T类型的参数,T继承Object对象
,所以通过断点我们可以看到把 int数组 整体作为一个对象,返回了一个 List<int[]>
,
,Arrays.asList不好用
,「那我们该如何解决呢?」
,方案一:Java8以上,利用Arrays.stream(arr).boxed()将装箱为Integer数组,方案二:声明数组的时候,声明类型改为包装类型:,我们将数组对象转成List数据结构之后,竟然不能进行增删操作了:,初始化一个字符串数组,将字符串数组转换为 List,在遍历List的时候进行移除和新增的操作
,抛出异常信息UnsupportedOperationException。
,根据异常信息java.lang.UnsupportedOperationException,我们看到他是从AbstractList里面出来的,让我们进入源码一看究竟,我们在什么时候调用到了这个 AbstractList 呢?
,其实 Arrays.asList(arr) 返回的 ArrayList 不是 java.util.ArrayList,而是 Arrays的内部类:,他是没有实现 AbstractList 中的 add() 和 remove() 方法,这里就很清晰了为什么不支持新增和删除,因为根本没有实现。
,一不小心修改了父List,却影响到了子List,在业务代码中,这会导致产生的数据发生变化,严重的话会造成影响较大的生产问题。
,第二个坑的源码中,完成字符串数组转换为List之后。
,我们将字符串数组的第三个对象的值修改为4,但是很奇怪在打印List的时候,发现List也发生了变化。,asList中创建了 ArrayList,但是他直接引用了原本的数组对象
,所以只要原本的数组对象一发生变化,List也跟着变化
,所以在使用到引用的时候,我们需要特别的注意。
,解决方案:
,重新new一个新的 ArrayList 来装返回的 List
,在第二个坑的时候,我们说到了 Arrays.asList 返回的 List 不支持增删操作,
,是因为他的自己实现了一个内部类 ArrayList,这个内部类继承了 AbstractList 没有实现 add() 和 remove() 方法导致操作失败。
,但是第三个坑的时候,我们利用 java.util.ArrayList 包装了返回的 List,进行增删操作还是会失败,那是为什么呢?
,删除方法逻辑:
,
,删除方法
,在foreach中操作增删,因为因为 modCount 会被修改,与第一步保存的数组修改次数不一致,抛出异常 ConcurrentModificationException
,在正确操作是什么?我总结了四种方式:
,
,正确操作
,阿里《Java开发手册》上提过
,
,[强制] ArrayList的sublist结果不可強转成ArrayList,否则会抛出ClassCastException
,异常,即java.util.RandomAccesSubList cannot be cast to java. util.ArrayList.
,说明: subList 返回的是ArrayList 的内部类SubList, 并不是ArrayList ,而是
,ArrayList的一个视图,対于SubList子列表的所有操作最终会反映到原列表上。
,,我猜问题是有八九就是出现在subList这个方法上了:,其实 SubList 是一个继承 AbstractList 的内部类,在 SubList 的构建函数中的将 List 中的部分属性直接赋予给自己。
,SubList 没有创建一个新的 List,而是直接引用了原来的 List(this.parent = parent),指定了元素的范围。
,所以 subList 方法不能直接转成 ArrayList,他只是ArrayList的内部类,没有其他的关系。
,因为是引用的关系,所以在这里也需要特别的注意,如果对原来的List进行修改,会对产生的 subList结果产生影响。,对subList产生的List做出结构型修改,操作会反应到原来的List上,ongChange也添加到了names中。
,如果修改原来的List则会抛出异常ConcurrentModificationException,原因:
,subList的时候记录this.modCount为3
,
,ConcurrentModificationException
,原来的List插入了一个新元素,导致this.modCount不第一次保存的不一致则抛出异常
,解决方案:在操作SubList的时候,new一个新的ArrayList来接收创建subList结果的拷贝
,在业务开发中的时候,他们经常通过subList来获取所需要的那部分数据
,在上面的例子中,我们知道了subList所产生的List,其实是对原来List对象的引用
,这个产生的List只是原来List对象的视图,也就是说虽然值切片获取了一小段数据,但是原来的List对象却得不到回收,这个原来的List对象可能是一个很大的对象
,为了方便我们测试,将vm调整一下 -Xms20m -Xmx40m,出现OOM的原因,循环1000次创建了1000个具有10万个元素的List
,因为始终被collect.subList(0, 1)强引用,得不到回收
,解决方式:
,在这里我们可以看到,只要用一个新的容器来装结果,就可以切断与原始List的关系
,学习数据结构的时候,我们就已经得出了结论
,●对于数组,随机元素访问的时间复杂度是0(1), 元素插入操作是O(n);
,●对于链表,随机元素访问的时间复杂度是O(n), 元素插入操作是0(1).
,元素插入对于链表来说应该是他的优势
,但是他就一定比数组快? 我们执行插入1000w次的操作:,看到在执行插入1万、10完次操作的时候,LinkedList的插入操作时间是 ArrayList的两倍以上
,那问题主要就是出现在linkedList的 add()方法上:,linkedList的 add()方法主要逻辑
,ArrayList的 add()方法:,根据试验的测试,我们得出了在实际的随机插入中,LinkedList并没有比ArrayList的速度快
,所以在实际的使用中,如果涉及到头尾对象的操作,可以使用LinkedList数据结构来进行增删的操作,发挥LinkedList的优势
,最好再进行实际的性能测试评估,来得到最合适的数据结构。
,CopyOnWrite,顾名思义就是写的时候会将共享变量新复制一份出来,这样做的好处是读操作完全无锁。
,CopyOnWriteArrayList的add()方法:,CopyOnWriteArrayList 内部维护了一个数组,成员变量 array 就指向这个内部数组,所有的读操作都是基于新的array对象进行的。
,因为上了独占锁,所以如果多个线程调用add()方法只有一个线程会获得到该锁,其他线程被阻塞,知道锁被释放, 由于加了锁,所以整个操作的过程是原子性操作
,CopyOnWriteArrayList 会将 新的array复制一份,然后在新复制处理的数组上执行增加元素的操作,执行完之后再将复制的结果指向这个新的数组。
,由于每次写入的时候都会对数组对象进行复制,复制过程不仅会占用双倍内存,还需要消耗 CPU 等资源,所以当列表中的元素比较少的时候,这对内存和 GC 并没有多大影响,但是当列表保存了大量元素的时候,
,对 CopyOnWriteArrayList 每一次修改,都会重新创建一个大对象,并且原来的大对象也需要回收,这都可能会触发 GC,如果超过老年代的大小则容易触发Full GC,引起应用程序长时间停顿。
,调用iterator方法获取迭代器返回一个COWIterator对象
,COWIterator的构造器里主要是 保存了当前的list对象的内容和遍历list时数据的下标。
,snapshot是list的快照信息,因为CopyOnWriteArrayList的读写策略中都会使用getArray()来获取一个快照信息,生成一个新的数组。
,所以在使用该迭代器元素时,其他线程对该lsit操作是不可见的,因为操作的是两个不同的数组所以造成弱一致性。,上面的demo中在启动线程前获取到了原来list的迭代器,
,在之后启动新建一个线程,在线程里面修改了第一个元素的值,移除了第二个元素
,在执行完子线程之后,遍历了迭代器的元素,发现子线程里面操作的一个都没有生效,这里提现了迭代器弱一致性。
,CopyOnWriteArrayList 迭代器是只读的,不支持增删操作
,CopyOnWriteArrayList迭代器中的 remove()和 add()方法,没有支持增删而是直接抛出了异常
,因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。,由于篇幅的限制,我们只对一些在业务开发中常见的关键点进行梳理和介绍
,在实际的工作中,我们不单单是要清除不同类型容器的特性,还要选择适合的容器才能做到事半功倍。
,我们主要介绍了Arrays.asList转换过程中的一些坑,以及因为操作不当造成的OOM和异常,
,到最后介绍了线程安全类CopyOnWriteArrayList的一些坑,让我们认识到在丰富的API下藏着许多的陷阱。
,在使用的过程中,需要更加充分的考虑避免这些隐患的发生。
,最后一张思维导图来回顾一下~
,
,List中的坑
© 版权声明
文章版权归作者所有,未经允许请勿转载。