1.简单介绍
定义
ArrayList是一种支持索引,插入有序,允许集合元素重复的单列集合(非线程安全),它实现了三个标志性接口(接口中什么也没有,只是单纯定义,用于标识) Serializable,Cloneable, RandomAccess
/*
实现这个接口的类的对象可以被jdk进行序列化,反序列化
序列化: 将java中的类或者对象转换成字节流,而字节就是二进制,计算机只认识二进制,将java对象转换成
字节流就可以实现网络传递,或者保存在文件中等用途
反序列化:将java对象转换成的字节数据转换成原来的对象
*/
public interface Serializable {
}
/*
实现这个接口的类可以被克隆,克隆又分为深克隆,浅克隆,这里不做讲解。
*/
public interface Cloneable {
}
/*
实现这个接口的类的对象支持随机访问,随机访问其实就是根据索引查数据,类似数组
*/
public interface RandomAccess {
}
2.原理
基本流程
1.ArrayList底层是用数组存放数据的,不过是动态数组(容量不够时自动扩容)。
2.调用无参构造方法时,会构建一个容量为0的数组,是懒加载的,只有在实际用到这个集合时对数组进行真正的初始化,第一次新增会初始化一个容量为10 的数组。
3.每次进行增加,插入等新增操作,会先判断数组容量,不够时扩容1.5倍。
重要属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/*
java对象序列化后保存在文件,或者其他地方时,
如果被修改了再反序列化成对象时,jvm会认为这个对象不是原来的对象,
加上唯一的版本号,也就是这个属性后,jvm会根据这个属性来判断是不是原来的对象。
*/
private static final long serialVersionUID = 8683452581122892189L;
/*
调用无参构造方法时,数组的默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/*
调用有参构造方法时,传入的容量为0时,数组的默认初始化数组就是这个属性的值
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/*
调用无参构造方法时,数组的默认初始化数组就是这个属性的值
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
ArrayList真正存放数据的数组
*/
transient Object[] elementData;
/*
ArrayList实际的数据数量
*/
private int size;
}
初始化
/*
无参构造,用到上面介绍的几个属性,这里只是验证一下
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
有参构造,用到上面介绍的几个属性,这里只是验证一下
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
如果传入的容量不为0,则直接进行初始化,不会调用到上面定义的常量。
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
//这里是传入的容量为负数,直接抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add()源码
这里只介绍add()的源码,本文主要是介绍ArrayList的初始化流程。
/*
这里的size属性是上面介绍的属性之一,也就是数组的实际容量
上面也介绍了,每次新增数据时,会先判断数组需不需要扩容
*/
public Boolean add(E e) {
/*
这个就是判断数组是不是需要扩容,按理说应该直接传入size,
但这里传入了size+1,可以理解成为了方便运算
*/
ensureCapacityInternal(size + 1);
//无论上面那个方法结果是什么,执行到这里数组的容量一定是够用的
elementData[size++] = e;
return true;
}
//这个方法是上面调用的方法,它又调用了两个方法,
//但你只需知道它是判断是否需要扩容,如果需要就进行数组扩容操作的方法就行
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 这个方法是用来计算数据的容量的
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/*这里判断如果elementData的地址是上面介绍的那个属性,
也就是通过无参构造实例化的,就说明它是第一次新增操作,此时数组的容量为0,
所以这里一定是DEFAULT_CAPACITY大的,也就是10,会创建一个长度为10的数组 */
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是就返回minCapacity,也就是数据的长度 + 1,
//注意是此时数组里数据的数量 + 1
return minCapacity;
}
//这个是进一步判断是否需要扩容的操作
private void ensureExplicitCapacity(int minCapacity) {
//这里是记录集合的修改次数,不用关心
modCount++;
//这里如果 数组的数据数量 + 1 - 数组的长度 > 0 ,说明需要扩容
if (minCapacity - elementData.length > 0)
//扩容操作
grow(minCapacity);
}
private void grow(int minCapacity) {
// 实际数组的长度
int oldCapacity = elementData.length;
// 对实际数组长度进行右移1位,也就除以2的1次方,再加上 原来的长度,也就是所谓的 1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这里判断扩容后的数组长度是不是能满足要求,不满足则用实际数据长度 + 1作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//这个就是数组的长度已经比int的最大数 -8 还大,这里一般不会发生,故不作讲解
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 重新创建一个扩容后的数组,并将原数组的数据复制过去
elementData = Arrays.copyOf(elementData, newCapacity);
}