arraylist源码解读
Posted 嘿boom
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了arraylist源码解读相关的知识,希望对你有一定的参考价值。
1.可以先看这一篇
https://www.cnblogs.com/zhangyinhua/p/7687377.html
2.arraylist.get()有两种,一种通过索引,一种通过对象。注意:
ArrayList<Integer> alist=new ArrayList<>();
alist.add(1);alist.add(2);
alist.get(1); //默认是通过索引,所以取出来的是2
3.arraylist的删除
3.1.for循环使用remove()删除坐标来删除元素,会使得忽略下一个被连续删除的节点。如下面这段代码,想删除b,c却只删除了b。因为删除了b之后,数组数据向前移动了一位,而i还要继续增大,所以就跳过了c
解决方法是:a.remove后i-- b.改为从后向前遍历。注意:for()里面i的初值为alist.size()-1
1 public class ArrayListTest { 2 public static void main(String[] args) { 3 ArrayList<String> alist=new ArrayList<>(); 4 alist.add("a"); 5 alist.add("b"); 6 alist.add("c"); 7 alist.add("d"); 8 System.out.println(alist.toString()); 9 for(int i=0;i<alist.size();i++) { 10 if(alist.get(i).equals("b") || alist.get(i).equals("c")) { 11 alist.remove(i); //应该是alist.reomove(i--); 12 } 13 } 14 System.out.println(alist.toString()); 15 } 16 } 17 18 19 //结果: 20 [a, b, c, d] 21 [a, c, d]
3.2 for each,iterator,listiterator遍历时候,不能用arraylist的修改方法同时修改,因为iterator在遍历时会检查modCount,而arraylist修改方法会增加modCount
解决方法:①iterator,listiterator要使用迭代器自身的修改方法
②使用CopyOnWriteArrayList
4.CopyOnWriteArrayList
1 //for each 这种方法不报错 2 3 List<String> list=new CopyOnWriteArrayList<>(); 4 list.add("a"); 5 list.add("b"); 6 list.add("c"); 7 for(String s:list) { 8 list.remove(s); 9 }
1 // 迭代器中,list的remove,也不报错 2 3 4 List<String> list=new CopyOnWriteArrayList<>(); 5 list.add("a"); 6 list.add("b"); 7 list.add("c"); 8 9 Iterator itr=list.iterator(); 10 while(itr.hasNext()) { 11 list.remove(itr.next()); 12 }
https://blog.csdn.net/linsongbin1/article/details/54581787
4.1,什么是写时复制(Copy-On-Write)容器?
写时复制是指:在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改。修改完成之后,将指向原来容器的引用指向新的容器(副本容器)。
4.2,写时复制带来的影响
①由于不会修改原始容器,只修改副本容器。因此,可以对原始容器进行并发地读。其次,实现了读操作与写操作的分离,读操作发生在原始容器上,写操作发生在副本容器上。
②数据一致性问题:读操作的线程可能不会立即读取到新修改的数据,因为修改操作发生在副本上。但最终修改操作会完成并更新容器,因此这是最终一致性。
适用场景(cyc总结)
CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。
但是 CopyOnWriteArrayList 有其缺陷:
-
-
- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
-
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
4.3,在JDK中提供了CopyOnWriteArrayList类和CopyOnWriteArraySet类,
5.clear()与new ArrayList<>()
clear()方法会将指向的内存中的数组清空,置为null,等待垃圾回收机制回收
但是new ArrayList<>(),会重新开辟一块内存,不影响之前的。
Arraylist复习思路:
1.继承的类和实现的接口,各接口代表什么意思(LIst, RandomAccess, Cloneable, Serializable)。处于框架图中的哪个位置
2.底层结构(数组)
3.变量的初始设置(容量10)
4.增, add()(数组初始化,扩容。 多种add方法)
5.删,clear() (数组各个位置置为null, 等待GC)/ remove()
6.改, set()
7.查, get() (两种方式,默认按索引取值,也可以取对象)
8.遍历,
1)常见循环for(;;)等
2)Iterator迭代器(注意for-each是本质上也是iterator)
a.ListIterator的常用方法
b.modCount和快速失败(不仅不允许多线程更改,单线程更改也有限制)/fail-fast在迭代器模式下起作用,因为迭代器要判断modCount,而list自带的增删改查会修改这一值,导致ConcurrentModificationException
c.遍历的同时如何增删改查(推荐用迭代器自带的)
9.RandomAccess,随机访问的含义
10,cloneable
11.序列化,elementData为什么是transient以及writeObject()与readObject()
12. CopyOnWriteArrayList没理解 https://www.jianshu.com/p/59d146148fdd
https://juejin.im/post/5aaa2ba8f265da239530b69e
13. Arrays.asList(T... a)出来的 ArrayList为什么不能add https://www.2cto.com/kf/201806/751606.html
fail-fast/fail-safe:
https://www.nowcoder.com/questionTerminal/95e4f9fa513c4ef5bd6344cc3819d3f7
一:快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。使用 Collections.synchronizedXXX() 创建的线程安全的集合也是 Fail-fast。
二:安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
以上是关于arraylist源码解读的主要内容,如果未能解决你的问题,请参考以下文章