JAVA集合:ArrayList详解
概述
ArrayList是我们日常写代码常用的集合之一,本文就 结合源码来介绍ArrayList常用方法
基本属性
/**
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空实例数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认大小的空数组实例,在第一次进入ensureCapacityInternal()方法时,会初始化长度为10
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存放元素的数组
* transient关键词的作用 : 当前修饰的参数不会被序列化
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 数组元素的数量
*/
private int size;
构造方法
/**
* 带容量参数的构造方法
* @param initialCapacity 初始容量大小
*/
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;
}
set()方法
/**
* 从数组中返回当前下标的元素
* @param index
* @return
*/
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* set方法,将元素设置到集合中
* @param index 要设置到的下标
* @param element 要设置的元素
* @return
*/
public E set(int index, E element) {
rangeCheck(index); // 数组下标越界检查
E oldValue = elementData(index); //直接根据index返回对应位置的元素(底层elementData是个数组)
elementData[index] = element; // 将传入的element代替当前index的元素
return oldValue; // 返回index位置原来的元素
}
get()方法
/**
* 获取当前下标的元素
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index); // 数组下标越界异常检查
return elementData(index); // 返回当前index的元素
}
add()方法
/**
* 在指定index插入元素
* @param index
* @param element
*/
public void add(int index, E element) {
// 数组下标越界检查
rangeCheckForAdd(index);
// 将modCount+1,并校验添加元素后是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将index位置及之后的所有元素向右移动一个位置(为要添加的元素腾出1个位置)
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将当前index设置为element元素
elementData[index] = element;
// 容量+1
size++;
}
/**
* 为当前集合添加一个元素
* @param e
* @return
*/
public boolean add(E e) {
// 校验添加元素后是否需要扩容
ensureCapacityInternal(size + 1);
// 在数组尾部添加元素并将size+1
elementData[size++] = e;
return true;
}
/**
* 赋初始值以及校验是否需要扩容
* @param minCapacity
*/
private void ensureCapacityInternal(int minCapacity) {
/**
* 判断当前数组是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* 如果是则将minCapacity设置DEFAULT_CAPACITY
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断添加元素后是否需要扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 修改次数+1
// 数组扩容方法 当添加元素后的长度大于数组长度则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 数组允许的最大长度
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 数组扩容方法
* @param minCapacity
*/
private void grow(int minCapacity) {
// 原来的容量
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + (旧容量 / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//当新数组长度小于minCapacity长度,则将新容量设为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 当新数组长度大于最大运行容量大
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 调用hugeCapacity()方法设置一个合适的容量
newCapacity = hugeCapacity(minCapacity);
// 将原数组元素拷贝到一个容量为newCapacity的新数组里面,底层调用的是 System.arraycopy()方法
// 设置elementData为新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
remove()方法
/**
* 通过下标删除元素
* @param index
* @return
*/
public E remove(int index) {
// 数组越界异常检查
rangeCheck(index);
// 修改次数+1
modCount++;
// 取出当前下标的值
E oldValue = elementData(index);
// numMoved : 需要移动的元素的个数
int numMoved = size - index - 1;
// 当移动的下标大于 0 时,将当前数组从当前下标的后一个开始复制
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将size-1,并将size-1位置的元素赋值为空(因为上面将元素左移了,所以size-1位置的元素为重复的,将其移除)
elementData[--size] = null; // clear to let GC do its work
// 返回被删除的元素
return oldValue;
}
/**
* 如果当前集合中存在一个与传入的参数相同的元素,则删除匹配到的第一个
* 否则不变
* @param o
* @return
*/
public boolean remove(Object o) {
// 元素非空判断
if (o == null) {
// 如果入参元素为空,则遍历数组查找是否存在元素为空,如果存在则调用fastRemove将该元素移除,并返回true表示移除成功
for (int index = 0; index < size; index++)
// 当数组中有一个元素为null时,则调用快速删除
if (elementData[index] == null) {
// 只有在删除对象时才会调用此方法,没有越界检查
fastRemove(index);
return true;
}
} else {
// // 如果入参元素不为空,则遍历数组查找是否存在元素与入参元素使用equals比较返回true,如果存在则调用fastRemove将该元素移除,并返回true表示移除成功
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/**
* 私有方法供上面的remove方法使用
*/
private void fastRemove(int index) {
// 修改次数+1
modCount++;
// 计算需要移动的元素的个数
int numMoved = size - index - 1;
// 如果需要移动的元素的个数>0,则将index+1位置及之后的所有元素,向左移动一个位置
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将size-1,并将size-1位置的元素赋值为空(因为上面将元素左移了,所以size-1位置的元素为重复的,将其移除)
elementData[--size] = null;
}
clear()方法
/**
* 将集合中的元素全部删除,当前元素置为空
*/
public void clear() {
// 修改次数+1
modCount++;
// clear to let GC do its work
// 遍历数组将所有元素置为空
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
ArrayList和LinkedList的比较
1、ArrayList的底层是动态数组实现的,LinkedList是基于链表实现的。
2、对于随机访问(get/set方法),ArrayList是通过下标直接定位到数组对应位置的节点,而LinkedList需要从头结点或尾结点开始遍历,直到找到目标结点。因此效率上ArrayList是要高于LinkedList的。
3、对于插入删除(add\remove方法),ArrayList需要移动目标结点后面的结点(通过System.arraycopy方法移动节点),而LinkedList只需要改变目标结点的前后节点的next或prev属性即可。因此在效率上,LinkedList要高于ArrayList。