一、ArrayList 简介
底层是数组队列,相当于动态数组。与Java的数组相比,容量可以动态增长。在添加大量元素前,可使用 ensureCapacity
增加ArrayList实例的容量。可减少递增式再分配的数量。
ArrayList
继承于 AbstractList
,实现了 List
, RandomAccess
, Cloneable
, java.io.Serializable
这些接口。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
List
: 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。RandomAccess
:标志接口,表明实现这个接口的List
集合是支持 快速随机访问 的。Cloneable
:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。Serializable
: 表明它可以进行序列化操作,j即将对象转换为字节流进行持久化存储或网络传输。
问题:ArrayList 和 Vector 的区别?
ArrayList
是List
的主要实现类,底层使用Object[]
存储,适用于频繁的查找工作,线程不安全 。Vector
是List
的古老实现类,底层使用Object[]
存储,线程安全。
问题:ArrayList 可以添加 null 值吗?
可以存储任何类型的对象,包括 null
值。不建议向ArrayList
中添加 null
值,会让代码难以维护比如忘记做判空处理就会导致空指针异常。
问题:ArrayList 和 LinkedList 区别?
-
是否线程安全?
都是不同步的,即不保证线程安全。
-
底层数据结构?
ArrayList
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构。 -
插入和删除是否受元素位置影响?
ArrayList
采用数组存储,插入和删除时间复杂度受元素位置的影响。如果不在末尾则需要 O(n) 因为可能需要执行元素移位操作。LinkedList
采用链表存储,所以在头尾插入和删除不受元素位置影响。但如何在指定位置则时间复杂度为O(n),因为需要遍历到指定位置。
-
是否支持快速随机访问?
LinkedList
不支持高效的随机元素访问,而ArrayList
(实现了RandomAccess
接口) 支持。 -
内存空间占用?
ArrayList
的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间。LinkedList
的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(存放直接后继和直接前驱以及数据)。
二、ArrayList类的成员变量
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组(用于空实例)。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小空实例的共享空数组实例。
//我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList 所包含的元素个数
*/
private int size;
}
ArrayList 扩容机制
private static final int DEFAULT_CAPACITY = 10;//默认初始容量大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList 的构造函数
(JDK8)ArrayList
有三种初始化的方式:
- 默认构造函数,使用初始容量为 10 构造一个空列表(无参构造)
以无参数构造方法创建 **ArrayList**
时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 带初始容量参数的构造函数。(用户自己指定容量)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大于0
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
- 构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回(如果指定的集合为null,throws NullPointerException)
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;
}
}
add()
以无参构造函数创建的 ArrayList 为例
将指定的元素追加到此列表的末尾。
public boolean add(E e) {
//添加元素之前,先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//ArrayList添加元素的实质就相当于为数组赋值
return true;
}
注意:JDK11 移除了
ensureCapacityInternal()
和ensureExplicitCapacity()
方法
ensureCapacityInternal()
private void ensureCapacityInternal(int minCapacity) {//得到最小扩容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 获取默认的容量和传入参数的较大值
}
ensureExplicitCapacity(minCapacity);
}
当要add()
进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。
ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) {//判断是否需要扩容
modCount++;
if (minCapacity - elementData.length > 0)// overflow-conscious code
grow(minCapacity);//调用grow方法进行扩容,调用此方法代表已经开始扩容了
}
- 当无参构造函数创建
ArrayList
,加入第一个元素时,elementData.length
为0(因为此时是一个空的List) - 执行
ensureCapacityInternal()
方法:使minCapacity = 10
。 - 执行
ensureExplicitCapacity()
方法:进入grow(minCapacity)
方法。
则add(e)
,添加第2到第10个元素,不会执行grow方法,因为数组容量为10。
直到添加第11个元素,mincapacity = 11 > elementData.length = 10
。进入 grow()
方法进行扩容。
grow()
//要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//ArrayList扩容的核心方法。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//oldCapacity为旧容量,newCapacity为新容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//将新容量更新为旧容量的1.5倍
//检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE
//如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则新容量大小为 MAX_ARRAY_SIZE 为 `Integer.MAX_VALUE - 8`
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);// minCapacity is usually close to size, so this is a win:
}
当add第一个元素时,数组扩容为10;当add第11个元素时,扩展为15。
java 中的
length
属性是针对数组。java 中的
length()
方法是针对字符串java 中的
size()
方法是针对泛型集合
hugeCapacity()
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
System.arraycopy() 和 Arrays.copyof()
System.arraycopy() 方法
/**
* 复制数组
* @param src 源数组
* @param srcPos 源数组中的起始位置
* @param dest 目标数组
* @param destPos 目标数组中的起始位置
* @param length 要复制的数组元素的数量
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length);
使用场景:列表的指定位置插入指定元素
- 先调用
rangeCheckForAdd
对 index进行界限检查;调用ensureCapacityInternal
保证 capacity足够。 - 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
Arrays.copyof() 方法
public static int[] copyOf(int[] original, int newLength) {
// 申请一个新的数组
int[] copy = new int[newLength];
// 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
场景:返回一个包含此列表的所有元素的数组,返回的数组的运行时类型是指定数组的运行时类型。
public Object[] toArray() {
//elementData:要复制的数组;size:要复制的长度
return Arrays.copyOf(elementData, size);
}
两者联系和区别
联系:copyOf()
内部实际调用了 System.arraycopy()
方法
区别:
arraycopy()
需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,且可以选择拷贝的起点和长度以及放入新数组中的位置。
copyOf()
是系统自动在内部新建一个数组,并返回该数组。
ensureCapacity()
内部没有被调用过,故是提供给用户调研。
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}