目录
java集合结构简介:
而我们这一篇博客所要分析的就是继承了List接口的 ArrayList类。
关于ArrayList
ArrayList的使用场景:
public static void main(String[] args) {
List list = new ArrayList();
list.add("name");
list.add(1,"address");
list.clear();
list.get(1);
list.isEmpty();
list.remove(1);
}
我们就从上面的使用场景中所用到的ArrayList类中的方法下手开始切入到其源代码中进行分析。
先看ArrayList类中的两个最最重要的属性(这两个属性可谓是ArrayList类中的精华,几乎所有的方法都是在为它们服务):
transient Object[] elementData;//存储ArrayList中存储的数据
private int size;//ArrayList中数据的长度(即个数)可见:初始值为0
构造方法之一:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
而在ArrayList类中定义有:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
所以,如果使用 new ArrayList()去创建ArrayList集合的时候,elementData指向的是一个存储Object类型数据的空数组。
add( )方法:
在ArrayList类中add( ) 有两种:
- 一种是将指定的元素添加到此列表(数组)的尾部;
- 一种是将指定的元素插入到此列表(数组)的指定位置。
1.将指定的元素添加到此数组的尾部的add( ):
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//确保elementData数组有合适的大小
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//这个方法主要用于为使用ArrayList默认构造方法时的elementData的长度赋值
//其中:private static final int DEFAULT_CAPACITY = 10;
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断元素数组是否为空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);//取较大值
}
return minCapacity;
}
//确保elementData数组有合适的大小
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) //elementData.length指的是数组的长度
grow(minCapacity);
}
//通过复制数组来扩大elementData数组的容量
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
现在我们写下以下代码,然后串一下ArrayList类中的方法的整个执行过程:
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 11; i++) {
list.add(i);
}
}
其中的List list = new ArrayList();调用了ArrayList类中的默认构造方法即:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可见,elementData指向了一个存储Object类型的空数组。
当执行for循环时,是在ArrayList中的elementData数组中加入了11个数据,下面我们分析一下整个过程中ArrayList集合中的elementData数组的大小变化以及其在ArrayList集合中调用各个方法的流程:
由上边这个过程再回过头看一下源代码中的这几个方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//确保elementData数组有合适的大小
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
- ensureCapacityInternal(int minCapacity)方法中传参size+1是因为每次add( )都是向elementData数组中添加一个数据,所以如果这个数组可以添加这个数据的话,它的size必然是在之前的size的基础上+1的。所以size+1这个数具有一定的意义,方便后边添加这个操作的实现(size+1即为此次数据添加到elementData数组中之后这个数组中数据的长度,注意这里不是数组的长度,而是数组中数据的长度);
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断元素数组是否为空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);//取较大值
}
return minCapacity;
}
- calculateCapacity(Object[] elementData, int minCapacity)在我看来“发挥功能”的时侯只有在通过ArrayList的默认构造方法创建ArrayList对象并且第一次调用add( )的时候,因为这个时候elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA成立,其他时候此方法都是直接将ensureCapacityInternal(int minCapacity)传入的minCapacity直接返回,所以这个方法可以理解成:给elementData数组赋初始大小,再没有其他的作用;
- ensureExplicitCapacity(int minCapacity)主要的操作就是在目前数组中存储不下这一数据时,去调用grow( )对elementData数组进行扩容,在一个就是使得modCount++(其主要是将“修改统计数”+1,该变量主要是用来实现fail-fast机制的);
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
- grow( )任务就是对elementData数组进行扩容,而其扩容的方式就是通过Array.copyOf( )方法,新建一个数组对象并且将elementData之前的数据全部复制到这个对象中;
2.将指定的元素插入到此列表(数组)的指定位置的add( )
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
可见,在指定位置插入指定元素和在数组的尾部加入数据的不同之处有两点:
- 在指定位置插入指定元素有下标越界检查,即rangeCheckForAdd(int index);
- 在数组的尾部加入数据时是在grow( )中调用Arrays.copy( )进行数组的拷贝,而在指定位置插入指定元素时是直接在add( )中调用System.arraycopy( );即arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 其中:Object src : 原数组 int srcPos : 从元数据的起始位置开始 Object dest : 目标数组 int destPos : 目标数组的开始起始位置 int length : 要copy的数组的长度 可知:在指定位置插入指定元素时,是将此位置之后的数据全部向后移一位。
- 它们在检查数组的大小是否可以存储下此数据或者此数组是否需要扩容这一问题上,都是使用ensureCapacityInternal(size + 1);
clear( )方法:
此方法用于移除此集合中的全部数据。其原代码如下:
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
整个clear( )方法中的操作分为两步:
- 将elementData数组中的数据全部赋值为null;这里有一点要说的就是,在for循环中没有使用elementData.length而是用的size,在这里说一下在ArrayList类中size 和 elementData.length之间的区别:
- 将size设置为0.
remove( int index):
删除指定数组下标处的数据。其源代码如下:
public E remove(int index) {
rangeCheck(index);//检查用户传入的下标是否越界
modCount++;
E oldValue = elementData(index);
//此变量用于判断用户删除的是否为最后一个数据
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
return oldValue;
}
关于ArrayList的总结:
- 实现原理采用动态对象数组实现,默认构造方法中创建一个空数组;
- 第一次添加元素时,扩展容量为10,之后的扩充算法为原来数组大小+原来数组大小/2;
- 由于其采用数组的形式进行存储,所以不适合进行删除和插入操作。通过remove( )也可以看出其删除数据时要对其位置后面的数据进行移位,这样子效率是很低的;
- 为了防止数组的动态扩充次数过多,建议创建ArrayList时,给定一个初始容量;
- ArrayList线程不安全,只适用于单线程环境。