ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
ArrayList
实现了List
, RandomAccess
,Cloneable
, java.io.Serializable
等接口。
ArrayList
实现了List
,提供了基础的添加、删除、遍历等操作。
ArrayList
实现了RandomAccess
,提供了随机访问的能力。(RandomAccess
是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在ArrayList
中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。)
ArrayList
实现了Cloneable
,可以被克隆。
ArrayList
实现了Serializable
,可以被序列化。
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 为所有空集合实例共用的空数组
* 当传入容量为0的时候使用 即new ArrayList(0) 的时候使用这个
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于以无参构造方法进行初始化的空实例的共享空数组。
* 当传入容量时使用 即new ArrayList()
* 与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(即10)个元素。(懒加载机制/懒汉式)
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList数据的数组(缓冲区)。ArrayList的容量是这个数组缓冲区的长度。
* 任何空数组列表的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* 将在添加第一个元素时扩展到DEFAULT_CAPACITY。
*/
transient Object[] elementData;
// non-private to simplify nested class access
/**
* 集合中元素的个数 不是elementData的大小
*/
private int size;
/**
* 数组最大长度。Java中的数组是由JVM构造的类,根据OOP-Klass二分模型,对象有对象头,而数组不能自己计算自己的长度,需要8字节
* 存储长度信息,所以是Integer.MAX_VALUE - 8
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 这是一个从父类AbstractList中继承而来的属性,记录了ArrayList被结构性修改的次数
* java.util包下的集合类都是快速失败(fail—fast)的,不能在多线程下发生并发修改(迭代过程中被修改)
*/
protected transient int modCount = 0;
(1)DEFAULT_CAPACITY
默认容量为10,也就是通过new ArrayList()创建时的默认容量。
(2)EMPTY_ELEMENTDATA
空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。
(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA
也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。
EMPTY_ELEMENTDATA
是为了优化创建ArrayList
空实例时产生不必要的空数组,使得所有ArrayList
空实例都指向同一个空数组。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是为了确保无参构成函数创建的实例在添加第一个元素时,最小的容量是默认大小10。
(4)elementData
真正存放元素的地方,使用transient
是为了不序列化这个字段。
至于没有使用private
修饰,后面注释是写的“为了简化嵌套类的访问”,但是加了private
嵌套类一样可以访问。
private
表示是类私有的属性,只要是在这个类内部都可以访问,嵌套类或者内部类也是在类的内部,所以也可以访问类的私有成员。
(5)size
真正存储元素的个数,而不是elementData
数组的长度。
modCount
这个参数定义在 java.util.AbstractList
抽象类中,其作用是:
记录已对该列表进行结构修改的次数。
结构修改是指更改列表大小(扩容等操作)或以其他方式干扰列表大小的方式,使其正在进行的迭代可能会产生错误的结果。
iterator 和 listIterator 方法返回的迭代器和列表迭代器的实现使用了此字段。如果此字段的值在迭代期间意外更改,则迭代器或列表迭代器的next、remove、previous、set、add方法将抛出ConcurrentModificationException。
面对迭代期间的并发修改,这提供了快速故障行为,而不是不确定的行为。
也就是说,由于 ArrayList
是非线程安全的,如果我们用在并发线程调用上,ArrayList
会使用 modCount
计数的方式,最大努力保证其方法的快速失败,而不会再将来产生不确定因素。但是,这个不是绝对有保证的,所以不应该并发的使用 ArrayList
。
快速失败(fail-fast)
在使用迭代器对集合对象进行遍历的时候,如果 A 线程正在对集合进行遍历,此时 B 线程对集合进行修改(增加、删除、修改),或者 A 线程在遍历过程中对集合进行修改,都会导致 A 线程抛出 ConcurrentModificationException 异常。 eg:HashMap、ArrayList
java.util 包的集合类就都是快速失败的
安全失败(fail-safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException 异常
java.util.concurrent 包下的类都是安全失败,比如:ConcurrentHashMap。
不传初始容量,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空数组,会在添加第一个元素的时候扩容为默认的大小,即10
public ArrayList() {
// 如果没有传入初始容量,则使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// 使用这个数组是在添加第一个元素的时候会扩容到默认大小10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
传入初始容量,如果大于0就初始化elementData
为对应大小,但是size是没有改变的
如果等于0就使用EMPTY_ELEMENTDATA
空数组,如果小于0抛出异常。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果传入的初始容量大于0,就新建一个数组存储元素
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATE
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果传入的初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
传入集合并初始化elementData
,这里会使用拷贝把传入集合的元素拷贝到elementData
数组中,
如果元素个数为0,则初始化为EMPTY_ELEMENTDATA
空数组。
//构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
//将指定集合转换为数组
elementData = c.toArray();
//如果c不是空集合
if ((size = elementData.length) != 0) {
// 检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//see 6260652 这个编号代表JDK bug库中的编号。可以去官网查看bug详情
// http:bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652
if (elementData.getClass() != Object[].class)
//将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果c是空集合,则初始化为空数组EMPTY_ELEMENTDATE
this.elementData = EMPTY_ELEMENTDATA;
}
}
为什么 c.toArray()
返回的有可能不是Object[]
类型呢?请看下面的代码:
public class ArrayTest {
public static void main(String[] args) {
Father[] fa = new Son[]{};
//class [LMyTest.Son;
System.out.println(fa.getClass());
List<String> strList = new MyList();
//class [Ljava.lang.String;
System.out.println(strList.toArray().getClass());
}
}
class Father{}
class Son extends Father{}
class MyList extends ArrayList<String> {
/*
子类重写父类的方法,返回值可以不一样
但这里只能用数组类型,换成Object就不行
应该算是java本身的bug
*/
@Override
public String[] toArray(){
return new String[]{"1","2","3"};
}
}
(1)检查是否需要扩容;
(2)如果elementData
等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
则初始化容量大小为DEFAULT_CAPACITY
,否则以传入容量为准;
(3)新容量是旧容量的1.5倍(oldCapacity + (oldCapacity >> 1)
),如果加了这么多容量发现比需要的容量还小,则以需要的容量为准;
(4)创建新容量的数组并把老数组拷贝到新数组。
//下面是ArrayList的扩容机制
//ArrayList的扩容机制提高了性能,如果每次只扩充一个,
//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
public boolean add(E e) {
// 检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//把元素插入到最后一位
elementData[size++] = e;
return true;
}
//判断是否要扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
*得到最小扩容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是==空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取“默认的容量10”和“传入参数”两者之间的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 判断是否需要扩容。
*/
private void ensureExplicitCapacity(int minCapacity) {
// 增加modCount,添加元素是结构性修改
modCount++;
// overflow-conscious code 说明下面这段代码是考虑了溢出的情况的
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
/**
* ArrayList扩容的核心方法。
*/
private void grow(int minCapacity) {
// overflow-conscious code说明下面这段代码是考虑了溢出的情况的
int oldCapacity = elementData.length;
//新容量为旧容量的1,5倍(扩容因子为1.5)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//新容量比需要的容量还小,则以需要的容量为准
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量已经超过最大容量了,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//以新容量拷贝出来一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 要分配的最大数组大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : MAX_ARRAY_SIZE;
//分配超过MAX_ARRAY_SIZE可能会导致OutOfMemoryError(内存溢出错误)
}
/**
* 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
在一次性添加大量元素前,可以手动调用此方法先进行扩容,以减少自动扩容时分配过大的容量空间。
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
//如果是true,minExpand的值为0,如果是false,minExpand的值为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It‘s already
// supposed to be at default size.
: DEFAULT_CAPACITY;
//如果最小容量大于已有的最大容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
(1)检查索引是否越界rangeCheckForAdd
(2)检查是否需要扩容ensureCapacityInternal
保证capacity足够大
(3)把插入索引位置后的元素都往后挪一位
(4)在插入索引位置插入元素
(5)大小加1
public void add(int index, E element) {
rangeCheckForAdd(index);//检查插入下标是否合法
//检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将index及其之后的元素往后挪一位,则index位置处就空出来了
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;//插入
size++;//大小加1
}
//一个为add和addAll方法做范围检查的方法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//它可以实现将一个数组的指定个数元素复制到另一个数组中
// param1: 源数组
// param2: 源数组要复制的起始位置
// param3: 目标数组
// param4: 目标数组放置的起始位置
// param5: 复制的长度
public static void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length)
/*
eg:
arrayCopy(arr1, 2, arr2, 5, 10);
意思是:将arr1数组里从索引为2的元素开始, 复制到数组arr2里的索引为5的位置, 复制的元素个数为10个.
Int[] arr1 ={1,2,3,4,5};
arrayCopy(arr1, 3, arr1, 2, 2); =>{1,2,4,5,5}.
意思是:将arr1从数字4开始 拷贝到arr1的数字3的位置, 拷贝2个数, 即元素4,5替换掉元素3,4
*/
求两个集合的并集
(1)拷贝c中元素到数组a
(2)检查是否需要扩容
(3)把数组a中的元素拷贝到elementData数组的尾部
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//集合c转成数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 检查是否需要扩容
System.arraycopy(a, 0, elementData, size, numNew);//将c中元素全部拷贝到elementData数组的最后
size += numNew;//大小增加c的大小
return numNew != 0;// 如果c不为空就返回true,否则返回false
}
获取指定索引位置的元素,时间复杂度为O(1)
(1)检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundException
异常,如果越下界抛出的是ArrayIndexOfOutOfBoundException
异常;
(2)返回索引位置处的元素;
public E get(int index) {
rangeCheck(index);//下标合法性检查
//返回数组index位置的元素
return elementData(index);
}
/**
检查给定的索引是否在合法范围
该方法不检查索引为负的情况,
因为它总是在访问数组之前使用如果index为负数则抛出ArrayIndexOutOfBoundsException
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
删除指定索引位置的元素,时间复杂度为O(1)
public E remove(int index) {
//检查是否越界
rangeCheck(index);
//remove属于结构化修改
modCount++;
//获取index位置的元素
E oldValue = elementData(index);
//如果index不是最后一位,则将index之后的元素往前挪一位
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个元素删除,帮助GC
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
从列表中删除指定元素的第一个匹配项,时间复杂度为O(n)
(1)找到第一个等于指定元素值得元素
(2)快速删除 : fastRemove(int index)
相对于remove(int index)
少了检查索引越界的操作,可见jdk将性能优化到极致。
public boolean remove(Object o) {
if (o == null) {
//遍历整个数组,找到元素第一次出现的位置,并快速删除
for (int index = 0; index < size; index++)
//如果要删除的元素为null,则以null进行比较,使用 ==
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//遍历整个数组,找到元素第一次出现的位置,并快速删除
for (int index = 0; index < size; index++)
//如果删除的元素不为null,则使用equals()方法进行比较
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/**
因为调用该方法都是内部计算index后调用的,所以不需要再校验index是否越界,也不需要返回oldValue。
不进行越界检查的删除
*/
private void fastRemove(int index) {
modCount++;
//如果删除的位置不是最后一位,则将index之后的元素往前挪一位
//numMoved 计算index之后的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
//将arr1从索引index+1的位置开始,复制到arr2索引为index,复制的元素个数为numMoved
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个元素删除,帮助GC
elementData[--size] = null; // clear to let GC do its work
}
/**
* 从此列表中删除所有索引为fromIndex (含)和toIndex(不含)之间的元素。
*将任何后续元素移动到左侧(减少其索引)。
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;//结构化修改
int numMoved = size - toIndex;//计算移动的数量
//从后往前移动数组元素进行覆盖
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
//将toIndex 后面的元素置为null 方便GC回收
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;//改变元素数量
}
set(int index, E element)方法可以将指定index的元素设置为指定element,并返回旧元素,时间复杂度O(1)。
public E set(int index, E element) {
//索引越界检查
rangeCheck(index);
//获取旧元素
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
求两个集合的交集
(1)遍历elementData
数组;
(2)如果元素在c中,则把这个元素添加到elementData
数组的w位置并将w位置往后移一位;
(3)遍历完之后,w之前的元素是两个集合共有的,w之后(包含w)的元素不是两集合共有的
(4)把w之后(包含w)的元素置为null,方便GC回收
public boolean retainAll(Collection<?> c) {
//集合c不能为null
Objects.requireNonNull(c);
//调用批量删除方法,这时的complement传入true,表示删除不包含在c的元素
return batchRemove(c, true);
}
/**
批量删除元素
complement为true表示删除c中不包含的元素 retainAll
complement为false表示删除c中包含的元素 removeAll
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
//使用读写两个指针同时遍历数组
//读指针每次自增1,写指针放入元素才加1
//这样不需要额外的空间,只需要在原有的数组上进行操作就可以了
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
// 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 正常来说r最后是等于size的,除非c.contains()抛出异常
if (r != size) {
// 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//将写指针之后的元素置为null,帮助GC
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)
size = w;
modified = true;
}
}
//有修改就返回true
return modified;
}
求两个集合的单方向差集,只保留当前集合中不在c中的元素,不保留在c中不在当前集体中的元素。
与retainAll(Collection c)
方法类似,只是这里保留的是不在c中的元素。
public boolean removeAll(Collection<?> c) {
//c不能为空
Objects.requireNonNull(c);
// 同样调用批量删除方法,这时complement传入false,表示删除包含在c中的元素
return batchRemove(c, false);
}
/**
* 将此ArrayList实例的容量调整为列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。
*/
public void trimToSize() {
//记录结构性修改的次数
modCount++;
//因为并发修改的可能,会造成 elementData 与 size 状态不统一,所以需要判断。这里也体现了尽最大努力的保证。
if (size < elementData.length) {
//如果 size == 0,则返回默认空数组 EMPTY_ELEMENTDATA ,否则重现调整 elementData 的大小。
elementData = (size == 0)
? EMPTY_ELEMENTDATA
//copyOf在底层是创建新数组,然后复制过去。而复制的操作是 native 的,也就是由其他语言底层实现。
: Arrays.copyOf(elementData, size);
}
}
(1)记录排序前的modCount
(2)调用Arrays.sort 进行排序
(3)比较排序前后的modCount是否发生改变,不一致抛出异常
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
//计算预期modCount 监测sort过程中是否出现结构性修改
final int expectedModCount = modCount;
//调用Arrays.sort
Arrays.sort((E[]) elementData, 0, size, c);
//排序后 出现modCount先后不一致 抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;//sort 也属于结构性修改
}
/**
* 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
*指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。
*返回的列表迭代器是fail-fast 。
*/
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
*返回列表中的列表迭代器(按适当的顺序)。
*返回的列表迭代器是fail-fast 。
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
*以正确的顺序返回该列表中的元素的迭代器。
*返回的迭代器是fail-fast 。
*/
public Iterator<E> iterator() {
//返回迭代器实例ListItr
return new Itr();
}
//返回元素个数
public int size() {
return size;
}
//返回集合是否为空(集合是否存在元素)
public boolean isEmpty() {
return size == 0;
}
//是否存在某个元素
public boolean contains(Object o) {
//调用indexOf
return indexOf(o) >= 0;
}
// 获取元素第一次出现的索引
//分null 和 正常元素 从头遍历 找到第一次出现的位置返回 不然返回-1
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//获取元素最后一次出现的索引 跟indexOf反着来就行
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//返回此 ArrayList 实例的浅副本。(元素本身不会被复制。)
public Object clone() {
try {
// clone 一个浅复制的副本,其中不包含元素。
ArrayList<?> v = (ArrayList<?>) super.clone();
//将数组复制一份新的
v.elementData = Arrays.copyOf(elementData, size);
//modCount 置为0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn‘t happen, since we are Cloneable
throw new InternalError(e);
}
}
//集合转数组方法
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
*返回一个数组,该数组按适当顺序(从第一个元素到最后一个元素)包含此列表中的所有元素。
返回数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则将其返回。 否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。
如果列表适合指定的数组并有剩余空间(即,数组中的元素多于列表),则紧接集合结束后的数组中的元素设置为 null。仅当调用者知道列表不包含任何null元素时,对其确定列表的长度很有用。
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
//如果指定的数组 小于 当前集合元素的个数
if (a.length < size)
则创建一个类型 T ,大小 size 的新数组。
// Make a new array of a‘s runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//否则先将elementData中的所有元素 复制到 a数组中
System.arraycopy(elementData, 0, a, 0, size);
//当 a 数组的长度大于 elementData 的长度时,将 elementData 在 a 数组中的最后一个元素的下一位置空。
if (a.length > size)
a[size] = null;
return a;
}
//清空当前集合的所有元素
public void clear() {
modCount++;
// 所有元素置为null 方便GC回收
for (int i = 0; i < size; i++)
elementData[i] = null;
//元素个数置为0
size = 0;
}
//用于lambda的
forEach
replaceAll
removeIf
forEachRemaining
/**
* An optimized version of AbstractList.Itr
* AbstractList.Itr 的优化版本
*/
// 注意,在迭代器迭代的过程中,列表的结构是不能修改的,否则会引发 ConcurrentModificationException 异常。
// 如果需要在迭代期间修改列表结构,只能使用迭代器自己的方法,而不能是 ArrayList 实例的方法。
private class Itr implements Iterator<E> {
int cursor; // index of next element to return 下一个需要返回的元素的游标索引,初始值为 0
int lastRet = -1; // index of last element returned; -1 if no such 最后一次操作返回的元素的索引,初始值 -1
int expectedModCount = modCount; //用于结构性修改的判断
Itr() {}
//判断是否存在下一个元素
public boolean hasNext() {
return cursor != size;
}
//返回迭代中的下一个元素。
@SuppressWarnings("unchecked")
public E next() {
//结构性修改判断
checkForComodification();
int i = cursor;
//如果下一个元素游标索引大于 size,则抛出异常
if (i >= size)
throw new NoSuchElementException();
//获取 ArrayList 实例的 elementData
Object[] elementData = ArrayList.this.elementData;
//这里对 length 的判断,是尽最大可能性的保证迭代器的快速失败。
//当并发调用的不确定性情况下,有可能修改了 elementData,但 expectedModCount = modCount 的情况发生,所以仍需要对 length 进行一层判断。
if (i >= elementData.length)
throw new ConcurrentModificationException();
//游标后移一位
cursor = i + 1;
//返回当前元素
return (E) elementData[lastRet = i];
}
// 从基础集合中移除此迭代器返回的最后一个元素(可选操作)。每次调用 next() 方法后,只能调用一次此方法。
// 如果在迭代进行过程中以其他方式(而不是通过调用此方法)修改了基础集合,则迭代器的行为是不确定的。
public void remove() {
//每次执行删除方法后,都会重置 lastRet 为 -1,以防止本方法被多次调用。以及不能一上来就 remove,需要先 next
if (lastRet < 0)
throw new IllegalStateException();
//结构性修改检查
checkForComodification();
try {
//调用 ArrayList 的 remove 实现
ArrayList.this.remove(lastRet);
//删除后,下一个元素游标 cursor 指向最后一次操作索引 lastRet。
//因为删除操作,会将剩余元素左移,所以下一个待返回的元素的游标索引就是当前删除位置的索引。
cursor = lastRet;
//重置 lastRet
lastRet = -1;
//修改结构性修改的计数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//对剩余的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常。
//如果指定了顺序,则按照迭代顺序执行操作。 该操作引发的异常将中继到调用方。
//参数为空则抛出异常
//函数式调用,lamaba 表达式
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
//参数判空
Objects.requireNonNull(consumer);
//当前列表元素的数量
final int size = ArrayList.this.size;
int i = cursor;
//如果游标超出元素数量,则不进行任何操作
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
//当游标值不等于 size ,并且没有结构性修改时,则进行循环遍历
while (i != size && modCount == expectedModCount) {
//将每一个元素用于 lamaba 表达式
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
//如果循环顺利完成,则一次性修改游标及lastRet的值
cursor = i;
lastRet = i - 1;
//最后在检查一次是否结构性修改
checkForComodification();
}
//结构性修改检查
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* An optimized version of AbstractList.ListItr
* AbstractList.ListItr 的改进版
*/
// ListIterator<E> :
// 列表迭代器,允许程序员在任一方向上遍历列表,在迭代过程中修改列表,并获取迭代器在列表中的当前位置。
// ListIterator没有当前元素。 它的光标位置始终位于通过调用 previous() 返回的元素和通过调用 next() 返回的元素之间。
// 长度为n的列表的迭代器具有 n + 1 个可能的光标位置,如下面的插入符号(^)所示:
// Element(0) Element(1) Element(2) ... Element(n-1)
//cursor positions: ^ ^ ^ ^ ^
//注意,remove() 和 set(Object) 方法不是根据光标位置定义的,它们被定义为对调用 next() 或 previous() 返回的最后一个元素进行操作。
//
private class ListItr extends Itr implements ListIterator<E> {
//迭代器可以选择遍历的起始位置,而这个位置也是 cursor 游标的初始值
ListItr(int index) {
super();
cursor = index;
}
//是否有前一个元素
public boolean hasPrevious() {
return cursor != 0;
}
//是否有下一个元素
public int nextIndex() {
return cursor;
}
//前一个游标的索引,注意不是元素的索引。
public int previousIndex() {
return cursor - 1;
}
// 返回列表中的上一个元素,并将光标位置向后移动。
// 可以重复调用此方法以向后遍历列表,也可以将其与对 next() 的调用来回混合。
// 请注意,交替调用 next 和 Previous 将重复返回相同的元素。即 next -> previous -> next 返回相同的元素
@SuppressWarnings("unchecked")
public E previous() {
//结构性修改检查
checkForComodification();
//获取上一个游标索引
int i = cursor - 1;
//游标边界检查
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//将游标后移一位(也就是左移一位)
cursor = i;
//返回元素
return (E) elementData[lastRet = i];
}
// 将 next() 或 previous() 返回的最后一个元素替换为指定的元素(可选操作)。
// 仅在最后一次调用 next 或 last 之后没有调用 remove() 和 add(E) 时才能进行此调用。
public void set(E e) {
//同上述注解中描述,remove() 和 add(E) 方法调用后,会重置 lastRet 为 -1
if (lastRet < 0)
throw new IllegalStateException();
//结构性修改检查
checkForComodification();
try {
//调用 ArrayList 实例的 set 实现
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 将指定的元素插入列表(可选操作)。
// 新元素将插入到 next() 方法返回的元素之前(如果有的话)与 previous() 方法返回的元素之后(如果有的话)
// 如果列表不包含任何元素,则新元素将成为列表上唯一的元素。
// 新元素将插入到隐式光标之前 :对 next 的后续调用将不受影响,而对 Previous 的后续调用将返回新元素。
// 此调用将 nextIndex 或 previousIndex 的调用返回的值增加一个。
public void add(E e) {
//结构性修改检查
checkForComodification();
try {
int i = cursor;
//调用 ArrayList 的 add 实现,将新元素插入到当前游标的位置
ArrayList.this.add(i, e);
//当前游标向前移动一位,也就是右移一位。
//对 next 的后续调用将不受影响,而对 Previous 的后续调用将返回新元素。
cursor = i + 1;
//重置 lastRet
lastRet = -1;
//修改结构性修改的计数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
/**
* Returns a view of the portion of this list between the specified
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If
* {@code fromIndex} and {@code toIndex} are equal, the returned list is
* empty.) The returned list is backed by this list, so non-structural
* changes in the returned list are reflected in this list, and vice-versa.
* The returned list supports all of the optional list operations.
*
* <p>This method eliminates the need for explicit range operations (of
* the sort that commonly exist for arrays). Any operation that expects
* a list can be used as a range operation by passing a subList view
* instead of a whole list. For example, the following idiom
* removes a range of elements from a list:
* <pre>
* list.subList(from, to).clear();
* </pre>
* Similar idioms may be constructed for {@link #indexOf(Object)} and
* {@link #lastIndexOf(Object)}, and all of the algorithms in the
* {@link Collections} class can be applied to a subList.
*
* <p>The semantics of the list returned by this method become undefined if
* the backing list (i.e., this list) is <i>structurally modified</i> in
* any way other than via the returned list. (Structural modifications are
* those that change the size of this list, or otherwise perturb it in such
* a fashion that iterations in progress may yield incorrect results.)
*
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*
* 返回此列表中指定的 fromIndex(包括) 和 toIndex(不包括) 之间的视图。如果fromIndex 和 toIndex 相等,则返回的列表为空。
* 此列表支持返回的列表,因此返回的列表中的非结构性更改会反映在此列表中,反之亦然。 返回的列表支持所有可选列表操作。
*
* 此方法消除了对显式范围操作(数组通常存在的那种范围)的需要。
* 通过传递subList视图而不是整个列表,可以将期望列表的任何操作用作范围操作。 例如,以下示例从列表中删除了一系列元素:
*
* list.subList(from, to).clear();
*
* 可以为 indexOf(Object) 和 lastIndexOf(Object) 构造类似的习惯用法,并且 Collections 类中的所有算法都可以应用于 subList。
*
* 如果后备列表(即此列表)以结构方式(而不是通过返回的列表)进行了修改,则此方法返回的列表的语义将变得不确定。
* 结构修改是指更改此列表的大小的结构修改,或者以其他方式干扰此列表的方式,即正在进行的迭代可能会产生错误的结果。
*
*/
public List<E> subList(int fromIndex, int toIndex) {
//边界检查
subListRangeCheck(fromIndex, toIndex, size);
//返回子列表实例 SubList
return new SubList(this, 0, fromIndex, toIndex);
}
//边界检查
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
//SubList 实现部分
//继承自AbstractList,其中有 modCount 成员变量。
//SubList 又重写了一遍 ArrayList 的一些方法,这说明如果想要修改子列表的结构,只能使用子列表提供的方法,而不是 ArrayList 的
private class SubList extends AbstractList<E> implements RandomAccess {
//父列表,这里不一定是最原始的列表,也会是父-子列表(子列表一样可以再有子列表)
private final AbstractList<E> parent;
//父列表的偏移量
private final int parentOffset;
//总偏移量,用于原始列表
private final int offset;
//当前 SubList 的元素数量
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount; //同步原始列表结构性修改计数 modCount
}
public E set(int index, E e) {
//边界检查
rangeCheck(index);
//结构性修改检查
checkForComodification();
//获取原始列表中,总偏移量处的旧值
E oldValue = ArrayList.this.elementData(offset + index);
//将原始列表中,总偏移量的位置设置新值
ArrayList.this.elementData[offset + index] = e;
//返回旧值
return oldValue;
}
public E get(int index) {
//边界检查
rangeCheck(index);
//结构性修改检查
checkForComodification();
//获取原始列表中,总偏移量处的旧值
return ArrayList.this.elementData(offset + index);
}
public int size() {
//结构性修改检查
checkForComodification();
return this.size;
}
public void add(int index, E e) {
//边界检查
rangeCheckForAdd(index);
//结构性修改检查
checkForComodification();
//使用父列表的 add 实现,所以使用父偏移量
//这里需要说明,为什么使用了父列表的方法,而不是像上面 set 方法使用原始列表呢?我的想法是:
//主要原因在 modCount,因为子列表也可以在产生子列表,当最后一层子列表进行 add 调用时,
//其实类似于递归的回溯操作,需要在每一层都去判断 modCount,只有完整的回溯到原始列表,才能保证其结构性的一致性。
//如果无论哪一层抛出异常,则会抛出异常从而保证其快速失败。
parent.add(parentOffset + index, e);
//同步父列表的 modCount
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
//边界检查
rangeCheck(index);
//结构性修改检查
checkForComodification();
//调用父列表的删除实现
E result = parent.remove(parentOffset + index);
//同步父列表的 modCount
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
//结构性修改检查
checkForComodification();
//调用父列表的 removeRange 实现
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
//同步父列表的 modCount
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
//边界检查
rangeCheckForAdd(index);
int cSize = c.size();
//如果参数集合没有元素,则返回 false
if (cSize==0)
return false;
//结构性修改检查
checkForComodification();
//调用父列表的 addAll 实现
parent.addAll(parentOffset + index, c);
//同步父列表的 modCount
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
public Iterator<E> iterator() {
return listIterator();
}
//使用匿名内部类,重写了一遍迭代器的实现
public ListIterator<E> listIterator(final int index) {
//结构性修改检查
checkForComodification();
//边界检查
rangeCheckForAdd(index);
final int offset = this.offset;
//大部分代码的含义同 ArrayList 的迭代器相同
return new ListIterator<E>() {
int cursor = index;
int lastRet = -1;
//获取原始列表的 modCount,用于后续结构性修改判断
int expectedModCount = ArrayList.this.modCount;
public boolean hasNext() {
return cursor != SubList.this.size;
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
//这里由于使用的是原始列表,所以使用 offset 总偏移量
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[offset + (lastRet = i)];
}
public boolean hasPrevious() {
return cursor != 0;
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[offset + (lastRet = i)];
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = SubList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[offset + (i++)]);
}
// update once at end of iteration to reduce heap write traffic
lastRet = cursor = i;
checkForComodification();
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//结构性修改,使用当前子列表
SubList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//非结构性修改,使用原始列表
ArrayList.this.set(offset + lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//除了边界条件有所不同,剩余代码均相同。可以往回参考
public void add(E e) {
checkForComodification();
try {
int i = cursor;
//结构性修改,使用当前子列表
SubList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//结构性修改检查
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
};
}
//这里就是子列表可以再生成子列表
public List<E> subList(int fromIndex, int toIndex) {
//边界检查
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, offset, fromIndex, toIndex);
}
//边界检查
private void rangeCheck(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//边界检查
private void rangeCheckForAdd(int index) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+this.size;
}
//结构性修改检查
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
//TODO() Spliterator<E> 暂时还未分析......
public Spliterator<E> spliterator() {
checkForComodification();
return new ArrayListSpliterator<E>(ArrayList.this, offset,
offset + this.size, this.modCount);
}
}
//1.8 的语法糖,用于 lamaba 表达式
@Override
public void forEach(Consumer<? super E> action) {
//参数判空
Objects.requireNonNull(action);
//获取 modCount
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
//将每一个元素传入函数式参数。每次循环都会判断 modCount
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//最后在判断一次 modCount
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
//TODO()
/**
* Creates a <em><a href="Spliterator.html#binding">late-binding</a></em>
* and <em>fail-fast</em> {@link Spliterator} over the elements in this
* list.
*
* <p>The {@code Spliterator} reports {@link Spliterator#SIZED},
* {@link Spliterator#SUBSIZED}, and {@link Spliterator#ORDERED}.
* Overriding implementations should document the reporting of additional
* characteristic values.
*
* @return a {@code Spliterator} over the elements in this list
* @since 1.8
*/
@Override
public Spliterator<E> spliterator() {
return new ArrayListSpliterator<>(this, 0, -1, 0);
}
//TODO()
/** Index-based split-by-two, lazily initialized Spliterator */
static final class ArrayListSpliterator<E> implements Spliterator<E> {
/*
* If ArrayLists were immutable, or structurally immutable (no
* adds, removes, etc), we could implement their spliterators
* with Arrays.spliterator. Instead we detect as much
* interference during traversal as practical without
* sacrificing much performance. We rely primarily on
* modCounts. These are not guaranteed to detect concurrency
* violations, and are sometimes overly conservative about
* within-thread interference, but detect enough problems to
* be worthwhile in practice. To carry this out, we (1) lazily
* initialize fence and expectedModCount until the latest
* point that we need to commit to the state we are checking
* against; thus improving precision. (This doesn‘t apply to
* SubLists, that create spliterators with current non-lazy
* values). (2) We perform only a single
* ConcurrentModificationException check at the end of forEach
* (the most performance-sensitive method). When using forEach
* (as opposed to iterators), we can normally only detect
* interference after actions, not before. Further
* CME-triggering checks apply to all other possible
* violations of assumptions for example null or too-small
* elementData array given its size(), that could only have
* occurred due to interference. This allows the inner loop
* of forEach to run without any further checks, and
* simplifies lambda-resolution. While this does entail a
* number of checks, note that in the common case of
* list.stream().forEach(a), no checks or other computation
* occur anywhere other than inside forEach itself. The other
* less-often-used methods cannot take advantage of most of
* these streamlinings.
*/
private final ArrayList<E> list;
private int index; // current index, modified on advance/split
private int fence; // -1 until used; then one past last index
private int expectedModCount; // initialized when fence set
/** Create new spliterator covering the given range */
ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
int expectedModCount) {
this.list = list; // OK if null unless traversed
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList<E> lst;
if ((hi = fence) < 0) {
if ((lst = list) == null)
hi = fence = 0;
else {
expectedModCount = lst.modCount;
hi = fence = lst.size;
}
}
return hi;
}
public ArrayListSpliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null : // divide range in half unless too small
new ArrayListSpliterator<E>(list, lo, index = mid,
expectedModCount);
}
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
if (i < hi) {
index = i + 1;
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
action.accept(e);
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null && (a = lst.elementData) != null) {
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
public long estimateSize() {
return (long) (getFence() - index);
}
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
// 删除此集合中满足给定谓词的所有元素。 在迭代过程中或谓词中引发的错误或运行时异常将中继给调用方。
// 用于 lamaba 表达式
@Override
public boolean removeIf(Predicate<? super E> filter) {
//参数判空
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
// 找到需要删除的元素。如果在此期间抛出任何异常,将不会对列表有任何的改动
int removeCount = 0; //需要删除元素的数量
//BitSet是一个位操作 set,所有位默认值 false,添加到其中的值的位置会被设置为 true。
final BitSet removeSet = new BitSet(size);
//设置modCount
final int expectedModCount = modCount;
final int size = this.size;
//循环遍历,找出所有需要删除的元素,每次遍历都会判断 modCount
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
//通过函数式编程接口 Predicate 的 test 方法,找出需要删除的元素
if (filter.test(element)) {
//如果找到,则设置 BitSet 对应位置为 true
removeSet.set(i);
//增加计数
removeCount++;
}
}
//判断modCount
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
//如果有需要删除的元素,则通过将剩余元素左移来填补空位的方式,删除元素
if (anyToRemove) {
//删除元素后的列表大小
final int newSize = size - removeCount;
//同样采用了左右指针的方式,保留 BitSet 中的 false 位
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i); //返回下一个 false 位
elementData[j] = elementData[i];
}
//左移之后,将新大小列表的剩余元素置空
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
//最后在判断一次 modCount
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
//结构性修改计数增加
modCount++;
}
//返回是否有元素被删除了
return anyToRemove;
}
// 使用基于 operator 对此列表每个元素计算后的结果,替换此列表中的每个元素。
// 用于 lamaba 表达式
@Override
@SuppressWarnings("unchecked")
// 代码基本相似,可往回参考
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
//根据指定的比较器的顺序对该列表进行排序。
//使用指定的比较器,此列表中的所有元素必须可以相互比较。即,c.compare(e1,e2) 不得对列表中的任何元素 e1 和 e2 抛出 ClassCastException。
//如果指定的比较器为null,则此列表中的所有元素都必须实现 Comparable 接口,并且应使用元素的自然顺序。
//该列表必须是可修改的,但无需调整大小。
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
}
(JDK8)ArrayList 有三种方式来初始化,构造方法源码如下:
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*默认构造函数,使用初始容量10构造一个空列表(无参数构造)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 带初始容量参数的构造函数。(用户自己指定容量)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大于0
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
*如果指定的集合为null,throws NullPointerException。
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
细心的同学一定会发现 :以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。 下面在我们分析 ArrayList 扩容时会讲到这一点内容!
补充:JDK7 new无参构造的ArrayList对象时,直接创建了长度是10的Object[]数组elementData 。jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式。JDK8的内存优化也值得我们在平时开发中学习。
这里以无参构造函数创建的 ArrayList 为例分析
add
方法/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
//添加元素之前,先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++] = e;
return true;
}
注意 :JDK11 移除了 ensureCapacityInternal() 和 ensureExplicitCapacity() 方法
(JDK7)可以看到 add 方法 首先调用了ensureCapacityInternal(size + 1)
//得到最小扩容量
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取默认的容量和传入参数的较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
当 要 add 进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。
此处和后续 JDK8 代码格式化略有不同,核心代码基本一样。
ensureExplicitCapacity()
方法如果调用ensureCapacityInternal()
方法就一定会进入(执行)这个方法,下面我们来研究一下这个方法的源码!
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
我们来仔细分析一下:
直到添加第 11 个元素,minCapacity(为 11)比 elementData.length(为 10)要大。进入 grow 方法进行扩容。
grow()
方法 /**
* 要分配的最大数组大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ArrayList扩容的核心方法。
*/
private void grow(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity /2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
//如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)! 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.
">>"(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
我们再来通过例子探究一下grow() 方法 :
当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。
当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。
以此类推······
这里补充一点比较重要,但是容易被忽视掉的知识点:
java 中的 length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.
java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
hugeCapacity()
方法。从上面 grow()
方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity()
方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE
,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8
。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//对minCapacity和MAX_ARRAY_SIZE进行比较
//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
System.arraycopy()
和 Arrays.copyOf()
方法阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及add(int index, E element)、toArray() 等方法中都用到了该方法!
System.arraycopy()
方法源码:
// 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义
/**
* 复制数组
* @param src 源数组
* @param srcPos 源数组中的起始位置
* @param dest 目标数组
* @param destPos 目标数组中的起始位置
* @param length 要复制的数组元素的数量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
场景:
/**
* 在此列表中的指定位置插入指定的元素。
*先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//arraycopy()方法实现数组自己复制自己
//elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
我们写一个简单的方法测试以下:
public class ArraycopyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = new int[10];
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;
System.arraycopy(a, 2, a, 3, 3);
a[2]=99;
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
结果:
0 1 99 2 3 0 0 0 0 0
源码:
/**
复制指定的数组,用空值截断或填充(如果需要),使副本具有指定的长度。
对于在原始数组和副本中都有效的所有索引,两个数组将包含相同的值。
对于在副本中有效但在原始副本中无效的任何索引,副本将包含null。
当且仅当指定的长度大于原始数组的长度时,这样的索引才会存在。得到的数组是newType类的数组。
U - 原始数组中对象的类
T - 返回数组中对象的类
T[] original - 要复制的数组
int newLength - 要返回的副本的长度
Class<? extends T[]> newType - 返回副本的类型
copyOf()在内部新建一个数组copy,调用arraycopy()将original内容复制到copy中去,并且长度为newLength和original长度之间的最小值,返回数组copy;
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
场景:
/**
以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
*/
public Object[] toArray() {
//elementData:要复制的数组;size:要复制的长度
return Arrays.copyOf(elementData, size);
}
个人觉得使用 Arrays.copyOf()
方法主要是为了给原有数组扩容,测试代码如下:
public class ArrayscopyOfTest {
public static void main(String[] args) {
int[] a = new int[3];
a[0] = 0;
a[1] = 1;
a[2] = 2;
int[] b = Arrays.copyOf(a, 10);
System.out.println("b.length"+b.length);
}
}
结果:
10
打印出b数组: 0 1 2 0 0 0 0 0 0 0
联系:
看两者源代码可以发现 copyOf()
内部实际调用了 System.arraycopy()
方法
相同点:
两个方法都属于浅度复制,即复制的只是原始对象的引用地址,在堆中公用一块内存,当修改原数组中某一个元素的数据时,复制完成产生的数组中的数据也会被修改。
深拷贝和浅拷贝主要针对于引用类型数据,
因为基本数据类型赋值后,改变新数据,不会影响到原来的数据;
而引用数据类型赋值后,改变新数据,将会影响到原来的数据,此时应该使用深拷贝和浅拷贝定义出一个跟原数据一样但互不影响的数据。
一维基本数据类型数组浅拷贝:
public class Depth_Shallow_Copy {
public static void main(String[] args) {
//一维数组 浅拷贝
int[] a = {1,2,3,4,5};
int[] copy = new int[a.length];
System.arraycopy(a,0,copy,0,5);
//比较数值
System.out.println(copy[0] == a[0]);//true
//修改数值
a[0] = 1111;
//原数组元素打印
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");//1111 2 3 4 5
}
System.out.println();
//复制的数组元素打印
for (int i = 0; i < copy.length; i++) {
System.out.print(copy[i]+" ");//1 2 3 4 5
}
}
}
二维数引用类型组浅拷贝:
二维数组中存放的是一维数组的引用
即二维数组的地址不同,但是其中的一维数组指向的地址是同一个内存地址
public class Depth_Shallow_Copy {
public static void main(String[] args) {
int a [][] ={{1,2,3},{4,5,6},{7,8,9}};
int[][] copy;
copy = Arrays.copyOf(a,a.length);
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
System.out.print(a[i][j]+" ");
}
System.out.println();
}
System.out.println();
//修改复制数组的元素
copy[0][0] = 1111;
//打印原数组的元素 可以发现修改复制数组元素后原数组元素跟着一起改变
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
System.out.print(a[i][j]+" ");
}
System.out.println();
}
}
打印的二维原数组:
1 2 3
4 5 6
7 8 9
修改复制数组的元素后 打印原数组:
1111 2 3
4 5 6
7 8 9
可以发现原数组跟着一起改变了,
如果一维数组存储的是引用类型,也是会相互影响的
区别:
arraycopy()
需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置
copyOf()
是系统自动在内部新建一个数组copy,调用arraycopy()将original内容复制到copy中去,并且长度为newLength和original长度之间的最小值,返回数组copy。
如果数组比较大,那么使用System.arraycopy()会比较有优势,因为其使用的内存复制,省去了大量的数组寻址访问等时间,而且它是一个静态本地方法,由虚拟机实现,效率自然高。
ensureCapacity
方法ArrayList 源码中有一个 ensureCapacity 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?
/**
如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
*
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It‘s already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数
我们通过下面的代码实际测试以下这个方法的效果:
public class EnsureCapacityTest {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
final int N = 10000000;
long startTime = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
list.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
}
}
运行结果:
使用ensureCapacity方法前:2158
public class EnsureCapacityTest {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
final int N = 10000000;
list = new ArrayList<Object>();
long startTime1 = System.currentTimeMillis();
list.ensureCapacity(N);
for (int i = 0; i < N; i++) {
list.add(i);
}
long endTime1 = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
}
}
运行结果:
使用ensureCapacity方法后:1773
通过运行结果,我们可以看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity 方法,以减少增量重新分配的次数。
资料来源于网络(主要来源彤哥读源码):
ArrayList 类实现的有 Serializable 接口,但成员变量elementData是有transient
修饰的(也就是elementData不会参与默认序列化),那ArrayList是怎么把元素序列化的呢?
其实仔细观察类里的方法你会发现有两个与序列化的流有关系的方法:writeObject
、readObject
在序列化过程中如果有这两个方法,会默认调用这两个方法进行用户自定义的序列化和反序列化,如果没有才走默认序列化。
那么我们知道作者的序列化是自定义了,那为什么这样做呢,为什么不直接使用默认序列化呢?
我们可以想下,每次扩容1.5倍,那这个数组实际会有一些空间扩容后还未被填充,如果使用默认序列化则会将null
也给序列化进去。
接下来我们来看一下自定义序列化方法具体的实现:
//Save the state of the ArrayList instance to a stream (that is, serialize it).
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
//防止序列化期间有修改
int expectedModCount = modCount;
//写出非transient非static属性(会写出size属性)
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
//写出元素个数
s.writeInt(size);
// 依次写出元素
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
//如果有修改,抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
//Reconstitute the ArrayList instance from a stream (that is, deserialize it).
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
//声明为空数组
elementData = EMPTY_ELEMENTDATA;
// 读入非transient非static属性(会读取size属性)
s.defaultReadObject();
// 读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
// 计算容量 (元素个数 并非 elementData的大小)
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
// 检查是否需要扩容
ensureCapacityInternal(size);
Object[] a = elementData;
// 依次读取元素到数组中
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
ArrayList 序列化采用的是自定义序列化方式
查看writeObject()
方法可知,先调用s.defaultWriteObject()
方法,再把size
写入到流中,再把元素一个一个的写入到流中。
一般地,只要实现了Serializable
接口即可自动序列化,writeObject()
和readObject()
是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()
方法中通过反射获取到writeObject()
这个方法。
在ArrayList
的writeObject()
方法中先调用了s.defaultWriteObject()
方法,这个方法是写入非static非transient
的属性,在ArrayList中也就是size
属性。同样地,在readObject()
方法中先调用了s.defaultReadObject()
方法解析出了size
属性。
elementData
定义为transient
的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。
通过跟踪ObjectOutputStream的writeObject()方法,调用链路如下所示:
writeObject -> writeObject0 -> writeOrdinaryObject -> writeClassDesc -> writeSerialData
序列化入口 -> 核心方法 -> 序列化 -> 类信息序列化 -> 类数据信息序列化
代码如下所示,可以看到会先判断是否有 writeObject 方法,如果有的话,会通过反射的方式调用序列化对象的writeObject方法,如果没有则使用默认序列化方式
writeSerialData 首先获取需要序列化的类(desc.getClassDataLayout()),遍历进行序列化。对于重写 writeObject 方法则通过反射调用该方法,否则使用默认的序列化方式。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
//1.自定义writeObject方法
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
// 调用自定义序列化 writeObject 方法
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {// 2. 默认序列化
defaultWriteFields(obj, slotDesc);
}
}
}
Java 序列化和反序列化(一)Serializable 使用场景
Java 序列化和反序列化(二)Serializable 源码分析 - 1
Java 序列化和反序列化(三)Serializable 源码分析 - 2
new ArrayList(0) 和 new ArrayList() 有什么区别
List list = new ArrayList(20) 扩容了几次
ArrayList 怎么实现数组动态扩容,扩容时机,扩容倍数,过程是怎么样的
ArrayList 怎么实现remove的,为什么remove具体元素性能差
Arraylist线程不安全的体现在哪里?如何解决
Arraylist遍历时进行删除时会报异常,用迭代器就不会抛出异常的原理是什么?
ArrayList 是怎么序列化的
Arraylist中add的过程
原文:https://www.cnblogs.com/weiweiCoding/p/14949355.html