前面介绍Java集合三大热门之二——HashMap和ConcurrentHashMap,那么剩下的一个就是ArrayList了,在使用列表时,除非特殊情况,一般都会用ArrayList。
一、简介
List允许有重复元素、且存储有序,实现主要有ArrayList、CopyOnWriteArrayList、Vector和LinkedList:
ArrayList:底层是数组,查询快,增删慢,线程不安全。
CopyOnWriteArrayList:底层是数组,读写分离,线程安全,效率较Vector高效,不过内存消耗较大。(add和remove等操作都加了ReentrantLock锁,当写的时候COW会先从原数组拷贝一份,然后在新的数组上做些操作,写完之后再将原数组引用指向新数组,读操作没有锁)
Vector:底层是数组,线程安全,但效率低,扩容时为原来的2倍。(所有方法都加了Synchronized锁)
LinkedList:底层是链表,查询慢,增删快,线程不安全。
二、源码解析
2.1 基础属性
//默认初始容量为10
private static final int DEFAULT_CAPACITY = 10;
//空实例数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认的空实例数组,在第一次调用ensureCapacityInternal时会初始化长度为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放元素的数组
transient Object[] elementData; // non-private to simplify nested class access
//数组当前元素的数量
private int size;
//数组允许的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.2 构造方法
//指定初始容量的有参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//使用默认容量的无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.3 替换指定位置元素
//用指定元素替换指定位置的元素
public E set(int index, E element) {
//检验是否数组越界
rangeCheck(index);
//根据index获取原元素
E oldValue = elementData(index);
//替换为新元素
elementData[index] = element;
//返回原元素
return oldValue;
}
//ps:下方多处用到
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
2.4删除元素
2.4.1 根据索引删除
//删除指定索引位置的元素
public E remove(int index) {
//检查是否越界
rangeCheck(index);
modCount++;
//获取要被删除的元素
E oldValue = elementData(index);
//计算需要移动的元素个数,
int numMoved = size - index - 1;
//判断是否需要移动。解释:如果size为5,index为4,那么5-4-1=0,因为index=4刚好是最后一个元素,无需移动。
if (numMoved > 0){
//将index+1之后的元素向左移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
}
//将元素个数-1,并将size-1位置置空(因为上面将元素左移了,所以size-1位置的元素是重复的;或者本身就是尾元素,直接置空)
elementData[--size] = null; // clear to let GC do its work
//返回删除元素的值
return oldValue;
}
2.4.2 根据元素值删除
//删除与指定值相同的元素,如果匹配到删除第一个与之匹配的元素,返回true,反之返回false
public boolean remove(Object o) {
if (o == null) {
//入参为null,遍历数组找到第一个为null的元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//调用fastRemove将改元素移除
fastRemove(index);
return true;
}
} else {
//同理,遍历数据找到第一个值为o的元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//删除指定index位置的元素
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
}
2.5 添加元素
2.5.1 添加到数组尾部
//在数组尾部增加一个元素
public boolean add(E e) {
//将modCount+1,并校验添加元素后是否需要扩容,确保能存下size+1个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
//在数组尾部添加元素,并将size+1
elementData[size++] = e;
return true;
}
2.5.2 添加到指定位置
public void add(int index, E element) {
//检查是否越界
rangeCheckForAdd(index);
//将modCount+1,并校验添加元素后是否需要扩容,确保能存下size+1个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index之后的元素向右移动1位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//index位置设置为要添加的元素
elementData[index] = element;
//元素个数+1
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
举个例子:
2.6 扩容
2.5小节在add方法添加元素之前会先调用ensureCapacityInternal方法,该方法有两个作用,一个是如果没初始化就先初始化,二是检验添加元素后是否需要扩容:
private void ensureCapacityInternal(int minCapacity) {
//判断ArrayList是否是无参构造创建的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//是无参构造,主要是给DEFAULTCAPACITY_EMPTY_ELEMENTDATA设置初始容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断添加元素后的大小是否超过数组大小
if (minCapacity - elementData.length > 0){
//超过,进行扩容
grow(minCapacity);
}
}
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
//原来的容量
int oldCapacity = elementData.length;
//新容量=老容量+老容量/2,也就是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0){
//说明新容量比需要的最小容量还小,把最小容量作为新容量
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0){
//说明超过数组最大大小阈值,调用hugeCapacity方法设置一个合适的容量
newCapacity = hugeCapacity(minCapacity);
}
// minCapacity is usually close to size, so this is a win:
//将原数组元素拷贝到容量为newCapacity的新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//这就不用解释了吧。。。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
三、如何实现序列化
大家可能没注意到,elementData数组使用transizent关键字修饰的,这就意味着序列化ArrayList时会忽略elementData,反序列化时会给其分配默认值,这肯定不行,那么它是如何实现序列化和反序列化的呢?
//存放元素的数组
transient Object[] elementData; // non-private to simplify nested class access
这有个知识点。
ps:在序列化过程中,如果序列化类中定义了writeObject和readObject方法,JVM会调用对象类里的writeObject和readObject方法完成序列化和反序列化。如果没有这俩方法,会默认调用ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。
所以ArrayList重写了writeObject和readObject方法来完成对elementData的序列化:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
//注意:是size
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
为什么要用transient?
ArrayList实际上是动态数组,每次放满后自动扩容,假如10个容量满了,这是又来一个元素要add,那么这时候就会扩容为15容量,但实际是11个元素,那么就会有4个null(容量越大,null值越多),而null值是没有意义的(除了add的null)。
使用Java自带的序列化机制会把没有意义的null值也序列化了,会造成空间浪费,所以ArrayList把元素数组设置为transient,并自己重写writeObject和readObject来实现序列化操作。
End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。