概括
动态数组、线程不安全,允许为空
一、类结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList
实现了List、RandomAccess、Cloneable、Serializable
接口,其中RandomAccess
表示快速随机访问,Cloneable
表示可克隆,Serializable
表示可序列化。
二、属性
//序列化ID
private static final long serialVersionUID = 8683452581122892189L;
//默认初始扩容大小
private static final int DEFAULT_CAPACITY = 10;
//空数组对象,当ArrayList(int initialCapacity)传参为零时赋值给elementData
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组对象,当ArrayList()时赋值给传参为零时赋值给elementData
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//底层存放数据的地方,可见ArrayList底层是数组
transient Object[] elementData;
//当前数组长度
private int size;
//数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
和DEFAULT_CAPACITY
的区别:
源码中这样表述的:We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
翻译为与EMPTY_ELEMENTDATA
的区别在于清楚第一次添加时扩容大小,有待理解
三、构造方法
1、无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使用无参构造,则elementData
为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空数组,大小为0,在第一次add时进行扩容,后面会讲
2、带int类型参数构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//给定大小大于零则创建给定大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//等于零时引用EMPTY_ELEMENTDATA空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
给定初始容量大小,在第一次add时判断是否进行扩容,此处疑惑:当传入合法参数时,代码跟入该构造方法后initialCapacity
始终为1?
3、带Collection参数构造方法
public ArrayList(Collection<? extends E> c) {
//集合转数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 不同Collection的toArray()实现不一样,因此此处需要判断返回类型重新构造
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果长度为空,则替换为EMPTY_ELEMENTDATA空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
//判断创建类型进行直接构造或者反射实例
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//调用本地方法System.arraycopy拷贝
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
四、常用API
1、add函数
添加单个元素
public boolean add(E e) {
//扩容,默认构造方法创建后在第一次add时首次扩容至10
ensureCapacityInternal(size + 1);
//放置元素
elementData[size++] = e;
return true;
}
扩容方法ensureCapacityInternal代码如下:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//如果是空数组,第一次扩容至默认大小10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//add会修改操作数
modCount++;
//如果当前元素个数+1超出数组容量则进行扩容
if (minCapacity - elementData.length > 0)
//扩容方法
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//使用位运算,扩容步长为1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//使用copy进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
解析:
a、如果初始是无参构造方法创建的空数组,第一次add时扩容至DEFAULT_CAPACITY
(10)大小
b、后续add操作则每次minCapacity为增加后的元素个数,判断如果minCapacity超过当前数组容量,则进行扩容
if (minCapacity - elementData.length > 0)
c、每次扩容大小为原来的1.5倍,采用位运算计算int newCapacity = oldCapacity + (oldCapacity >> 1);
d、Arrays.copyOf()
方法底层调用了本地方法System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength) )
指定位置插入元素
public void add(int index, E element) {
//首先判断插入下标是否越界
rangeCheckForAdd(index);
//判断扩容
ensureCapacityInternal(size + 1);
//从插入位置整体向后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//插入元素替换下标元素
elementData[index] = element;
//元素个数+1
size++;
}
//判断下标是否越界
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
解析:
a、插入前首先判断下标是否越界
b、判断是否需要扩容
c、从下标位置开始使用复制方法整体向后移位,然后替换下标位置为插入元素
d、因为涉及大量元素移位,所以插入效率低
批量插入
public boolean addAll(Collection<? extends E> c) {
//将集合转为数组
Object[] a = c.toArray();
int numNew = a.length;
//判断是否需要扩容
ensureCapacityInternal(size + numNew);
将数组a复制到list数组中
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
指定位置批量插入
public boolean addAll(int index, Collection<? extends E> c) {
//判断下标是否越界
rangeCheckForAdd(index);
//将集合转为数组
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
2、移除元素
按元素删除
public boolean remove(Object o) {
//判断传入是否为null
if (o == null) {
//遍历元素,可删除位于首位和中间为null的元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//查找要删除的元素下标后删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//删除元素,涉及到删除位以后元素整体位移
private void fastRemove(int index) {
//操作数+1
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
按下表删除
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;
//返回删除的元素
return oldValue;
}
解析:
a、按元素删除可删除首位和中间位置为null的元素
b、按元素删除返回值为true/false,按下标删除返回值为删除的元素
c、删除也涉及元素位移,比较耗时
3、查找元素
public E get(int index) {
//判断下标是否越界
rangeCheck(index);
return elementData(index);
}
没啥好说的
4、修改元素
public E set(int index, E element) {
//凡是涉及下标的都会首行校验下标
rangeCheck(index);
E oldValue = elementData(index);
//替换为新元素
elementData[index] = element;
//返回旧元素
return oldValue;
}
也没啥好说的,凡是涉及下标都要进行下标越界校验
五、总结
Arraylist基于数组实现,随机访问和遍历效率高,插入和删除涉及元素位移,效率比较低;扩容方式为第一次到10,以后超出容量则扩容至1.5倍