看一下继承、实现了什么
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
我们首先需要明白并且牢记在内心的是,ArrayList本质上是一个数组,但是与Java中基础的数组所不同的是,它能够动态增长自己的容量。
通过ArrayList的定义,可以知道ArrayList继承了AbstractList,同时实现了List,RandomAccess,Cloneable和java.io.Serializable接口。
那么这些提供了什么功能呢?
/**
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 使用默认大小的共享的空数组
* 与EMPTY_ELEMENTDATA的区别:当向数组添加第一个元素时,知道数组该扩容多少.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* ArrayList基于该数组实现,用该数组保存数据
* ArrayList的容量是该数组的长度
* 数组的默认大小为DEFAULT_CAPACITY
* transient:在实现Serializable接口后,将不需要序列化的属性前面加上transient
*/
transient Object[] elementData; // 没有被私有化是为了简化内部类访问
/**
* ArrayList的大小(实际所含元素的个数)
*/
private int size;
/**
* 被修改的次数
*/
protected transient int modCount = 0;
/**
* 数组的最大值
* -8是因为要保留数组的一些头信息
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 给定容量的构造器
*/
public ArrayList(int initialCapacity) {
//如果给的容量为正数,则数组初始化就是这个值
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//如果为0就采用默认
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//否则抛出异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 无参构造器,默认容量为10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 带泛型参数的构造器
*/
public ArrayList(Collection c) {
//先把参数转为数组类型
//toArray()方法重载自
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 官方的bug,说不一定能返回Object[]类型
if (elementData.getClass() != Object[].class)
//如果真的不是Object[]类型,就强制转回Object[]类型
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果传入的容器参数为0,就把他替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
* 在数组末尾增加元素
* 先不管ensureCapacityInternal的话,
* 这个方法就是将一个元素增加到数组的size位置上,然后size=size+1。
* 再说回ensureCapacityInternal,它是用来扩容的,准确说是用来进行扩容检查的。下面我们来看一下整个扩容的过程
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 下面的操作可分为下面两步
// elementData[size] = e
// size=size+1
elementData[size++] = e;
return true;
}
这个add(E e)函数涉及很多函数,下面逐一分析
// 检查容量大小
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算容量大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 从默认容量和穿进来的容量选择一个最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//直接返回minCapacity
return minCapacity;
}
// 扩容判断
private void ensureExplicitCapacity(int minCapacity) {
//记录修改的次数
modCount++;
// 判断是否需要扩容
// elementData.length数组的大小,并不是数组的元素个数,size才是
if (minCapacity - elementData.length > 0)
// 进行真正的扩容操作
grow(minCapacity);
}
重点:grow是真正的扩容操作,所以单独拿出来讲
/**
* 进行真正的扩容操作
*/
private void grow(int minCapacity) {
// 获取旧的列表大小
int oldCapacity = elementData.length;
// 新的容量是在原有的容量的基础上+50% 右移一位就是二分之一
// 上面1处>>表示右移,也就是相当于除以2,减为一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容一半之后还不足,则新的容器大小等于minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新的容量大于MAX_ARRAY_SIZE,则进行hugeCapacity()操作
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制原先的数组,并给他一个新容量
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 最大不能超过Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
// 因为一旦大小超过了Integer.MAX_VALUE,数值就会为负数
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
扩容机制如下:
/**
* 删除指定位置的元素
* 把这个元素后面的元素全部往左移一位(下标减一)
*/
public E remove(int index) {
// 数组下标越界检查
rangeCheck(index);
// 记录修改次数
modCount++;
// 得到要删除的元素
E oldValue = elementData(index);
// 需要移动的元素的数量=实际元素个数-当前要删除元素下标-1
int numMoved = size - index - 1;
// 如果这个值大于0,说明后续还有元素需要左移
if (numMoved > 0)
// 被删除元素的下标为index
// 删除原理:index之后的所有元素都往前移一位,覆盖前面的元素,总共需要移动numMoved个元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后一个元素的值赋值为null,这样就可以被GC回收了
elementData[--size] = null;
// 返回删除的值
return oldValue;
}
主要分析ArrayList中的add()函数为什么线程不安全
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
通过上面的分析可以看到add()函数中有两个步骤,这两个步骤都在多线程的情况下都有可能会出现问题
首先分析第一个步骤:ensureCapacityInternal(size+1);
我这里画了一个EXCEL方便分析
然后分析第二个步骤elementData[size++] = e;
其实这里就是size++;不是线程安全
ArrayList 是线性表(数组)
LinkedList 是链表的操作
Collections工具类有一个synchronizedList方法
可以把list变为线程安全的集合,但是意义不大,因为可以使用Vector
通过对比ArrayList和Vector的源码可以清楚的看到,Vector之所以线程安全是因为加了大量的synchronized
注意1和2是浅拷贝(shallow copy)。
为了更好的理解它们的区别我们假设有一个对象A,它包含有2对象对象A1和对象A2
对象A进行浅拷贝后,得到对象B但是对象A1和A2并没有被拷贝
对象A进行深拷贝,得到对象B的同时A1和A2连同它们的引用也被拷贝
参考:
https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/2-ArrayList.md
https://juejin.im/post/5b2c5eefe51d4558c0442e95?utm_source=gold_browser_extension
http://developer.51cto.com/art/200905/124592.htm