前言
本文主要是对ArrayList源码中add(E e)方法进行一个简单的学习,更好的了解ArrayList添加数据的过程
提示:本篇文章是在学习过程中的一个记录,还存在一些问题,希望各位的补充
一、add方法源码
以下代码截取了ArrayList中的add(E e)的方法的实现过程,构造方法只截取了传入初始容量和缺省构造方法两种。
package com.chengqi.javase.learningexamples.sourcecodeanalysis;
import java.util.Arrays;
/**
* 对于ArrayList的源码中的add(E e)方法进行一个分析
* 该示例代码是以一个Student类作为要添加的数据的类型
*
* 未实现泛型
* 该ArrayList集合,调用无参构造方法时,会构造出一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA,容量为0
* 添加一个元素之后,会扩容为10,每次扩容的大小为1.5倍
*/
public class ArrayList {
/**
* 默认的初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例,
* 即构造一个指定初始容量为0的一个空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
*用于默认大小的空实例的共享空数组实例,
* 在调用无参构造方法,且未给该ArrayList赋值时,构造一个空数组实例
* 我们将其与EMPTY_ELEMENTDATA分开来,以了解添加第一个元素时要膨胀(扩容)多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList元素的数组缓冲区。
* ArrayList的容量是此数组缓冲区的长度。
* 添加第一个元素时,任何elementData==DEFAULTCAPACITY_empty_elementData的空ArrayList
* 都将扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData;
/**
*ArrayList实际存储的大小(它包含的元素个数)
*/
private int size;
/**
* 记录了结构性改变的次数。
* 结构性改变指的是那些修改了列表大小的操作,在迭代过程中可能会造成错误的结果。
* madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用,
* 当进行next()、remove()、previous()、set()、add()等操作时,
* 如果madCount的值意外改变,
* 那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。
*/
protected transient int modCount = 0;
/**
* 要分配的数组的最大大小。
* 一些vm在数组中保留一些头字。
* 尝试分配较大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 构造具有指定初始容量的空列表。
* @param initialCapacity the initial capacity of the list(列表的初始容量)
* @throws IllegalArgumentException if the specified initial capacity
* is negative(如果指定的初始容量为负数-->参数不合法异常)
*/
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);
}
}
/**
* 构造一个初始容量为10的空列表,其在未添加元素时默认为容量为0
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 将指定的元素追加到此列表的末尾。
* @param student element to be appended to this list(要附加到此列表的元素)
* @return 返回一个布尔类型,表示是否添加成功
*/
public boolean add(Student student) {
ensureCapacityInternal(size + 1);
elementData[size++] = student;
return true;
}
/**
*确保内部容量
* @param minCapacity 最小容量(刚好存入)
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算容量
* @param elementData 存储ArrayList元素的数组缓冲区。
* @param minCapacity 所需的最小容量
* @return 计算后的最小容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//当调用的是无参构造器,且第一次添加元素时,进入该if分支
if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA ) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
*确保许可容量
* @param minCapacity 所需的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
//进行了修改操作,记录一下内部结构更改次数
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
*增加容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
* 当扩容后的容量大于MAX_ARRAY_SIZE(要分配的数组的最大大小)时,调用hugeCapacity(minCapacity)方法
* @param minCapacity the desired minimum capacity(期望的最小容量)
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//预分配冗余空间的方式来减少内存的频繁分配,采用了空间换时间思想
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
*大容量
* 当minCapacity(期望的最小容量)大于MAX_ARRAY_SIZE(数组的最大大小)时,
* 将Integer.MAX_VALUE(整数的最大值)作为扩容容量,不大于则将MAX_ARRAY_SIZE(数组的最大大小)
* 作为扩容容量
* @param minCapacity 期望的最小容量
* @return Integer.MAX_VALUE(整数的最大值) MAX_ARRAY_SIZE(数组的最大大小)
*/
private static int hugeCapacity(int minCapacity) {
//当两个比较大的数相加时,可能存在结果为负数的情况,当出现这种情况时,抛出内存溢出错误
if (minCapacity < 0)
throw new OutOfMemoryError(); //内存溢出
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
}
二、实现过程
1.创建ArrayList对象
通过创建对象,调用相应的构造方法,完成初始化操作
1.1 当调用无参构造器
/**
* 构造一个初始容量为10的空列表,其在未添加元素时默认为容量为0
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从上面的代码可以知道,其在未添加数据时的容量其实等于0
1.2 当通过构造器传入初始容量
/**
* 构造具有指定初始容量的空列表。
* @param initialCapacity the initial capacity of the list(列表的初始容量)
* @throws IllegalArgumentException if the specified initial capacity
* is negative(如果指定的初始容量为负数-->参数不合法异常)
*/
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);
}
}
从这里可以发现,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是空数组,这是为了将ArrayList中默认的空数组与我们自己创建的初始容量为0的空数组区分开来。
2 调用add方法
/**
* 将指定的元素追加到此列表的末尾。
* @param student element to be appended to this list(要附加到此列表的元素)
* @return 返回一个布尔类型,表示是否添加成功
*/
public boolean add(Student student) {
ensureCapacityInternal(size + 1);
elementData[size++] = student;
return true;
}
调用add方法,如果没有传入初始容量,其默认容量则为0;所以便须确保其内部数组的容量
/**
*确保内部容量
* @param minCapacity 最小容量(刚好存入)
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
通过对内部数组容量与理想最小容量进行比较,当为默认初始空数组,则直接将其理想最小容量变为10,不是则直接将size+1(刚好可以存下)作为理想最小容量
/**
* 计算容量
* @param elementData 存储ArrayList元素的数组缓冲区。
* @param minCapacity 所需的最小容量
* @return 计算后的最小容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA ) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
当理想最小容量大于内部数组容量,则需对内部数组进行扩容操作
/**
*确保许可容量
* @param minCapacity 所需的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容操作:
oldCapacity + (oldCapacity >> 1) —>扩容打了原来的1.5倍,
在这便存在一个问题,当我的扩容后的容量已经超过了数组的最大容量
/**
*增加容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
* 当扩容后的容量大于MAX_ARRAY_SIZE(要分配的数组的最大大小)时,调用hugeCapacity(minCapacity)方法
* @param minCapacity the desired minimum capacity(期望的最小容量)
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//预分配冗余空间的方式来减少内存的频繁分配,采用了空间换时间思想
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
当扩容后的容量大于数组最大容量时,如果我的理想最小容量不大于数组的最大大小,就将数组的最大大小作为扩容后的容量;如果我的理想最小容量大于数组的最大大小,则将整数的最大值作为扩容后的容量。
/**
*大容量
* 当minCapacity(期望的最小容量)大于MAX_ARRAY_SIZE(数组的最大大小)时,
* 将Integer.MAX_VALUE(整数的最大值)作为扩容容量,不大于则将MAX_ARRAY_SIZE(数组的最大大小)
* 作为扩容容量
* @param minCapacity 期望的最小容量
* @return Integer.MAX_VALUE(整数的最大值) MAX_ARRAY_SIZE(数组的最大大小)
*/
private static int hugeCapacity(int minCapacity) {
//当两个比较大的数相加时,可能存在结果为负数的情况,当出现这种情况时,抛出内存溢出错误
if (minCapacity < 0)
throw new OutOfMemoryError(); //内存溢出
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
三 总结
我们需要知道,newCapacity(扩容后的数组容量)与minCapacity是有区别的。
四 理解上的不足
源码中的modCount,对其的了解不够深入
/**
* 记录了结构性改变的次数。
* 结构性改变指的是那些修改了列表大小的操作,在迭代过程中可能会造成错误的结果。
* madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用,
* 当进行next()、remove()、previous()、set()、add()等操作时,
* 如果madCount的值意外改变,
* 那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。
*/
protected transient int modCount = 0;
MAX_ARRAY_SIZE的计算,Integer.MAX_VALUE - 8;为什么要减去8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;