ArrayList、Vector、Stack
类继承
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
- Serializable:支持序列化(标记作用,内部无方法)
- 序列化是把内存对象转成别的东西
- 反序列化是把别的东西转成内存对象
- 比如一个对象序列化后存入本地文件,反序列化后又成为一个对象
- 再比如前端请求传入的是字符串,后端接收到后是一个对象
这个比喻不恰当,因为 json 不需要序列化,java类不实现序列化接口也可以,本质上后端是结构映射而非类型还原
- Cloneable:支持克隆(标记作用,内部无方法)
- Object 在调用
clone()方法时 JVM 会检测类是否实现了 Cloneable 接口,如果没有会报错 Object.clone()方法是浅拷贝,所以 Arraylist 自己重写过clone方法
- Object 在调用
- RandomAccess:支持随机访问(标记作用,内部无方法)
- 哪种数据结构最适合随机访问?当然是数组了,所以链表实现的 LinkedList 就没实现 RandomAccess 接口
- 作用就是允许算法根据此标记选择最优实现
比如Collestions.binarySearch()方法里就有判断,如果实现了 RandomAccess 就用数组的方式直接使用下标访问,不然就链表方式顺序访问
- List:定义列表的基本操作(接口用来定义行为,实现类具体实现)
- List 继承了 Collection 接口,Collection 继承了 Iterable 接口
- Collection、Iterable 也是定义了一些行为,比如迭代器、转数组等
- AbstractList:父接口的一些默认实现,避免重复编写功能,比如
indexOf()、lastIndexOf()等
类成员
类属性
// 数组默认容量(首次添加元素时才真正分配)
private static final int DEFAULT_CAPACITY = 10;
// 这俩货都是空数组,在显示表达和资源优化上有区别(在下面配合构造方法来解释更易理解)
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正存放数据的数组(transient:在序列化时会忽略 transient 标注的字段,ArrayList 自己实现了 readObject 和 writeObject 方法来序列化)
transient Object[] elementData; // non-private to simplify nested class access
// 元素个数
private int size;
// 修改次数
protected transient int modCount = 0;
内部类
// 基础的迭代器,只有移除和顺序遍历
private class Itr implements Iterator<E> {
@SuppressWarnings("unchecked")
public E next() {}
public void remove() {}
}
// 增强的迭代器具有 Itr 的功能,增强了逆序遍历的能力,除了移除还能添加和修改元素
private class ListItr extends Itr implements ListIterator<E> {
@SuppressWarnings("unchecked")
public E previous() {}
public void set(E e) {}
}
// 调用 sublist() 获取 ArrayList 中的一部分(子列表),操作这个子列表 JVM 层面有一些优化处理
private static class SubList<E> extends AbstractList<E> implements RandomAccess {
}
// 对 ArrayList 进行高效遍历和拆分操作,JDK8 引入
// 调用 spliterator() 这个方法获取 ArrayListSpliterator 对象
// 并行流 parallelStream() 内部也会调用 spliterator() 获取 ArrayListSpliterator
final class ArrayListSpliterator implements Spliterator<E> {
}
构造函数
// 不带参数,使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 硬性指定长度
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { // 大于 0,创建指定长度的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 如果指定 0,使用 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 传入一个列表
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
// 正常添加
} else {
// 如果传入的是 null,使用 EMPTY_ELEMENTDATA
elementData = EMPTY_ELEMENTDATA;
}
}
-
EMPTY_ELEMENTDATA
new ArrayList(0); // 显式指定初始容量为0 new ArrayList(collection); // 当传入空集合时带了参数表示要精确所需大小,不需要预分配的空间,说多少就多少。尊重用户的选择
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
new ArrayList(); // 无参构造器使用不带参数,那就使用默认的长度
添加元素
public boolean add(E e) {
modCount++;
add(e, elementData, size); // 调用这个方法来完成添加
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 扩容
elementData = grow();
elementData[s] = e; // s 是元素个数(如果数组目前已经存在 2 个元素的话,就应该在下标是 2 的位置插入)
size = s + 1;
}
扩容
private Object[] grow() {
return grow(size + 1); // size 是当前元素个数,数组最小长度就要+1(参数形参翻译过来也就是最小容量)
}
private Object[] grow(int minCapacity) {
// 获取当前容量
int oldCapacity = elementData.length;
// 情况1:非首次扩容或非默认空数组
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 计算新容量
int newCapacity = ArraysSupport.newLength(...);
// 创建新数组并拷贝数据
return elementData = Arrays.copyOf(elementData, newCapacity);
}
// 情况2:首次扩容且是默认空数组
else {
// 直接创建新数组(默认容量或指定最小容量)
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
新容量怎么计算来的,扩容多少?
// oldCapacity:当前数组长度(也就是元素个数,前提是长度=个数)
// minCapacity:最小增长量(调用 add() 导致的扩容就是1,调用 addAll() 导致的扩容可能 > 1)
// oldCapacity >> 1:右运算,旧容量的一半(推荐的增长量)
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// 推荐是原长度的一半,但是有可能原长度的一半不够( addAll 添加 1w 个元素),所以要取大的 max(最小增长, 首选增长)
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
// SOFT_MAX_ARRAY_LENGTH 是个静态常量(Integer.MAX_VALUE - 8)为什么要减 8?
// Integer.MAX_VALUE 不同的 vm 规范不一致,避免超过真正 Integer 的最大值,保险起见设置一个软最大长度
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// 新数组长度如果大于 SOFT_MAX_ARRAY_LENGTH,放弃1.5倍扩容策略,仅满足基本要求
return hugeLength(oldLength, minGrowth);
}
}
private static int hugeLength(int oldLength, int minGrowth) {
// 原长度 + 最小增量。这个基本要求也满足不了就报错
int minLength = oldLength + minGrowth;
if (minLength < 0) { // 为什么要判断 < 0?Integer最大值+1后是负数
throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
return SOFT_MAX_ARRAY_LENGTH;
} else {
return minLength;
}
}
- 默认扩容为原来长度的1.5倍,但不绝对
- 1.5 倍只是推荐值,当超出 Integer 最大值(软最大值)时扩容就不是 1.5 倍,是需要多少就扩多少
- 如果调用无参构造创建的列表,添加元素时第一次扩容(也是设置初始长度)扩容后是 10
- 数组最大长度不是 Integer 最大值,因为不同虚拟机 Integer 最大值可能不一致,所以减8(软最大值,最大值附近)
总结
- 基于数组实现
- 元素有序(添加顺序)、可以添加重复元素、允许元素为null、线程不安全
- 支持随机访问,实现了 RandomAccess 接口
- 插入、删除元素时间复杂度 O(n),尾部插入
O(1)但是可能扩容 - 获取元素时间复杂度 O(1)
- 插入、删除复杂换来获取时的便捷,所以适用于查询多的场景
- 插入、删除元素时间复杂度 O(n),尾部插入
- 扩容
- 默认初始容量 10(无参构造时首次添加才分配)
- 扩容公式:
新容量 = max(最小需求, 旧容量 * 1.5)(扩容1.5不是绝对的) - 大容量处理:超过
Integer.MAX_VALUE - 8抛出OutOfMemoryError(扩容1.5不是绝对的) - 不会自动缩容,但可通过
trimToSize()释放多余空间
- 两种迭代器
Itr:基础迭代器(单向,支持remove)ListItr:增强迭代器(双向,支持add/set/remove)
- 内存与性能优化
new ArrayList()使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA,首次添加才分配默认容量EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA区分构造意图,以人为本,客户是上帝transient Object[] elementData,序列化时忽略底层数组,自己实现了 readObject 和 writeObject 仅序列化有效元素
Vector
| 特性 | ArrayList (JDK 1.2+) | Vector (JDK 1.0, 遗留类) |
|---|---|---|
| 线程安全 | 非线程安全 | 线程安全 (所有方法用 synchronized 修饰) |
| 扩容机制 | 新容量 = max(最小需求, 旧容量 * 1.5) |
新容量 = max(最小需求, 旧容量 * 2) (默认翻倍) |
| 初始容量 | 10 (首次添加元素时分配) | 10 (构造时直接分配) |
| Java 版本 | JDK 1.2+ (现代集合框架) | JDK 1.0 (遗留类,仍保留但已过时) |
Stack
Stack 继承自 Vector,扩展了栈的特性(后进先出)

浙公网安备 33010602011771号