目录
ArrayList简介
ArrayList
的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity
操作来增加 ArrayList
实例的容量。这可以减少递增式再分配的数量。
ArrayList
继承于 AbstractList
,实现了 List
, RandomAccess
, Cloneable
, java.io.Serializable
这些接口。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
初始化
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组(用于空实例)。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小空实例的共享空数组实例。
//我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList 所包含的元素个数
*/
private int size;
这里提前说明几个变量有助于后面理解:
1. EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是空数组,不过前者容量为0,后者初始容量为0,当有元素进来时候按照DEFAULTCAPACITY增加,也就是容量直接由1->10(假设开始往空数组只添加一个元素)
2.transient关键字出现的时候不参与类的序列化,用来保留传进来的数组数据
3.size为实际有的元素个数,而后面会出现的数组.length,是数组的长度,后者大于等于size。比如刚开始空集合加入一个元素,但是扩容逻辑要求先一下子扩到10,所以size为1,.length为10.
构造函数
注意点:
1. 无参构造用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,像上面所说的,当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。 在下面的add方法中会详细的讲。
2.带容量/集合作为参数的有参构造,参数为0是默认给的是另一个空数组EMPTY_ELEMENTDATA
3.最后一种有参构造,.toArray()方法出来的不是object对象情况下,要进行copyOf方法进行拷贝
注意:toArray是浅拷贝,只是让elementData指向c.toArray的地址,所以当不是object对象时要进行深拷贝copyOf,生成一个新的数组。
/**
* 默认初始容量大小
*/
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;
}
}
扩容机制
add,一个参数
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这里可以看到,minCapacity取得是10和传进来的size+1的最大值,就在这个时候出现了minCapacity和size+1的不一致的可能。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
如果此时minCapacity满足不了数组的长度,开始扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
传进来的minCapacity就是arg0,args2就是数组长度+数组长度二进制数右移1位(*1/2),也就是1.5倍,如果还不够,就让 最后的长度为需要的arg0,如果已经大到大于2^31 - 1,那就执行hugeCapacity;如果都没有这些意外情况,就将数组按照1.5倍原长度拷贝成一个新数组
private void grow(int arg0) {
int arg1 = this.elementData.length;
int arg2 = arg1 + (arg1 >> 1);
if (arg2 - arg0 < 0) {
arg2 = arg0;
}
if (arg2 - 2147483639 > 0) {
arg2 = hugeCapacity(arg0);
}
this.elementData = Arrays.copyOf(this.elementData, arg2);
}
在下面的代码中,当 `minCapacity` 小于0时会抛出 `OutOfMemoryError` 异常,而不是说 `minCapacity` 小于0本身会导致内存溢出。这是因为在 Java 中,通常情况下我们不希望数组的容量为负数,因为数组的长度应该是一个非负整数。如果 `minCapacity` 为负数,通常表示发生了某种错误,比如传入了非法的参数,所以抛出 `OutOfMemoryError` 异常来提醒开发者程序出现了问题。
//比较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;
}
add,两个参数
涉及到index就要检查是否在合法范围内
arrayCopy会在后面写,参数为(原数组,原数组起始位置,目标数组,目标数组起始位置,想复制的长度)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
其他方法
remove
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
注意是将元素置空,而不是把对应的位置也删掉了,等着系统 GC。
clear
同样是全员置空
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
subList
我们看到代码中是创建了一个ArrayList 类里面的一个内部类SubList对象,传入的值中第一个参数时this参数,其实可以理解为返回当前list的部分视图,真实指向的存放数据内容的地方还是同一个地方,如果修改了sublist返回的内容的话,那么原来的list也会变动。
所以阿里巴巴开发规约中有一条如下,底层是根据modCount一不一致去判断有没有并发修改异常,所以在ArrayList中,modCount很重要,记得++。
public List<E> subList(int arg0, int arg1) {
subListRangeCheck(arg0, arg1, this.size);
return new ArrayList.SubList(this, 0, arg0, arg1);
}
Arrays.copyOf
public static int[] copyOf(int[] original, int newLength) {
// 申请一个新的数组
int[] copy = new int[newLength];
// 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
还有一个常见的相关方法: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);
可以发现 copyOf()
内部实际调用了 System.arraycopy()
方法
区别:
arraycopy()
需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf()
是系统自动在内部新建一个数组,并返回该数组