一、基本方法解析
1、构造器
ArrayList有三种创建方式
ArrayList的底层实现方式为数组,在创建ArrayList时,底层会创建数组对象Object[]
1.1 无参构造器
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
通过这种方式创建ArrayList,底层对创建一个空的数据,这个数组对象是 static final 类型的,即该数组对象在初始化的是否就会被创建,并且不能够被改变。
1.2 自带数组长度的的构造方式
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
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);
}
}
不难看出,这种方式在创建ArrayList对象时,会进行一系列的判断,根据你传入的参数不同,进行不同的构造方式。
- ① 当传入的数组长度值大于0时,会创建一个特定长度的数组对象。
- ② 当传入的数组长度值等于0时,会创建一个空的Object数组对象,基本上跟无参的构造方法是一样。
- ③ 第三种情况就是你传入的参数是非法参数,那么,会抛出异常“Illegal Capacity: initialCapacity”。
这里要特别注意的是,elementData数组对象被transient所描述,因为Arraylist实现了java.io.Serializable接口,即在序列化的时候,Arraylist会使用Serializable的默认序列化方式,而被transient关键字修饰的属性会在序列化时被忽略,不进行序列化。
1.3 传入一个集合Collection对象,构造ArrayList对象
transient Object[] elementData;
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;
}
}
2、元素的添加:add()、addAll()
Arraylist提供了四种的添加新增元素的方式,两种add(),以及两种addAll()。
2.1 直接在集合的结尾添加元素:add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在这个过程中,会做一下的操作:
- ① 判断当前ArrayList对象的 elementData 属性是否为空,如果为空,则判断当前你要传入的集合长度 size+1 和会提供的默认集合长度10的大小,拿较大的一个值作为集合长度。如果 elementData 不为空,则直接拿传入时的 size+1 作为当前集合的长度(size是在添加当前数据之前集合的长度).
核心代码1:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断当前集合的属性elementData是否为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY 默认为 10,取较大的一个值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断新的集合长度与数组长度的大小
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- ② 把获取到的集合的长度与当前数组elementData的长度进行比较,如果当前集合的长度小于其属性对象elementData的长度,则直接在当前数组中数据的后面添加数据;如果当前集合的长度大于其属性数组对象elementData的长度,则需要增加数组的长度(添加规则:在原有的数组长度的基础上在增加一半,作为一个新的数组长度,在判断新的长度与当前集合的长度大小,如果新的数组长度依然比集合长度小,则直接把集合长度作为数组长度,否则,用新的数组长度。然后再把这个新的数组长度与可分配的数组的最大大小进行比较,得到最终的新的数组大小),然后根据得到最新的数组长度创建一个新的数组,把elementData中的数组copy到其中,再将这个新的数组赋值给elementData,然后把要添加的元素添加进去。
核心代码2:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取当前的数组长度
int oldCapacity = elementData.length;
// 新的数组长度为:原来的数组长度 + 原来数组长度的1/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 比较新的数组长度与新的集合长度的大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 比较新的数组长度与jvm所允许的最大数组长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 创建一个新的数组,将原来的数组信息添加进去,然后再赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.2 将某个元素添加到集合的指定位置
public void add(int index, E element) {
// 首先判断当前传入的位置是否合法,即在0-size之间
rangeCheckForAdd(index);
// 判断是否需要添加数组长度,此处的操作同上add()中的操作
ensureCapacityInternal(size + 1); // Increments modCount!!
// 用System.arraycopy()方法将数组index-1之后的所有数据往后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 在index的位置加入新的元素
elementData[index] = element;
// 更新size
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
基本错作流程如下:
- ① 首先判断当前传入的位置是否合法,即在0-size之间
- ② 判断是否需要添加数组长度,此处的操作同上add()中的操作
- ③ 用System.arraycopy()方法将数组index-1之后的所有数据往后移动一位
- ④ 在index的位置加入新的元素
- ⑤ 更新size
2.3 将整个Collection对象中所有的元素添加到ArrayList中
public boolean addAll(Collection<? extends E> c) {
// 通过toArray()方法将Collection转化为数组a
Object[] a = c.toArray();
// 获取传入的集合的长度
int numNew = a.length;
// 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
ensureCapacityInternal(size + numNew); // Increments modCount
// 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
System.arraycopy(a, 0, elementData, size, numNew);
// 更新size
size += numNew;
return numNew != 0;
}
基本操作流程如下:
- ① 通过toArray()方法将Collection转化为数组a
- ② 获取传入的集合的长度
- ③ 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
- ④ 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
- ⑤ 更新size
2.4 将整个Collection对性中的元素从特定位置开始全部加入到Arraylist
public boolean addAll(int index, Collection<? extends E> c) {
// 首先判断当前传入的位置是否合法,即在0-size之间
rangeCheckForAdd(index);
// 通过toArray()方法将Collection转化为数组a
Object[] a = c.toArray();
// 获取传入的集合的长度
int numNew = a.length;
// 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
ensureCapacityInternal(size + numNew); // Increments modCount
// 通过System.arraycopy()方法将原本elementData中index-1之后的所有元素向后移动 numNew位
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 通过System.arraycopy()方法将传入数组中的数据添加到elementData中
System.arraycopy(a, 0, elementData, index, numNew);
// 跟新size的值
size += numNew;
return numNew != 0;
}
基本操作流程如下:
- ① 首先判断当前传入的位置是否合法,即在0-size之间
- ② 通过toArray()方法将Collection转化为数组a
- ③ 获取传入的集合的长度
- ④ 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
- ⑤ 通过System.arraycopy()方法将原本elementData中index-1之后的所有元素向后移动 numNew位
- ⑥ 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
- ⑦ 更新size