源码:ArrayList & LinkedList
List 接口的实现类(Java 1.2+)
- 有序,有下标,可重复。
- 线程不安全,效率高。
ArrayList
ArrayList
基于数组存储元素。
成员变量
数据类型 | 说明 | |
---|---|---|
DEFAULT_CAPACITY |
int | 默认初始容量(10 ) |
EMPTY_ELEMENTDATA |
Object[] | 空数组 |
DEFAULTCAPACITY_EMPTY_ELEMENTDATA |
Object[] | 默认容量的空数组 |
elementData |
Object[] | 存储集合元素的数组 |
size |
int | 当前存储的实际元素个数 (ArrayList 长度) |
空数组对比:
- EMPTY_ELEMENTDATA:使用有参构造方法时使用,避免重复创建空数组。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA:使用无参构造方法时使用,确保初始化容量。
无参构造函数
操作:仅将
elementData
设置默认容量空数组,没有其它操作。首次调用
add()
时,进行初始化操作。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
add()
// 1
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 2
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
// 3
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 4
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
首次调用
此时数组为
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,容量为
0
。
- add():添加元素。
- 传入当前需要的最小容量(当前元素个数 + 1),确保数组容量。
- 添加元素到数组末尾。
- ensureCapacityInternal():确保数组容量。
- 判断
elementData
是空数组,待初始化容量为 10。 - 确保数组可用容量。
- 判断
- ensureExplicitCapacity():确保数组可用容量。
minCapacity
表示当前需要的最小容量,elementData.length
表示当前实际容量。- 实际容量必须大于最小容量,否则需要扩容。
- grow():数组扩容。
- 定义新容量 = 旧容量的
1.5
倍(仅确定扩容容量)。 - 确保新容量
>= minCapacity
(首次调用时 if 不成立,赋值新容量 =minCapacity
)。 - 若新容量超过最大限制,进行特殊操作(避免
OOM
异常)。 - 将 element 实际扩容为新容量(首次调用 add() 时,到此才具有默认容量 10)。
- 定义新容量 = 旧容量的
非首次调用
- add()
- ensureCapacityInternal():
- 数组
elementData
非空,跳过。 - 确保数组可用容量。
- 数组
- ensureExplicitCapacity():判断最小容量是否大于实际容量。
- 是:扩容。
- 否:返回。
- grow():
- 新容量 = 旧容量的 1.5 倍。
- 新容量必定大于最小容量,通常不会超过最大限制。
- 实际扩容。
LinkedList
LinkedList
基于双向链表存储元素,定义内部类
Node<E>
作为存储单位。
private static class Node<E> {
// 数据
E item;
// 后继结点
Node<E> next;
// 前驱结点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
成员变量
数据类型 | 含义 | |
---|---|---|
size | int | 当前存储的实际元素个数 (LinkedList 长度) |
first | Node<E> | 头结点 |
last | Node<E> | 尾结点 |
添加元素
已知一个双向链表(头结点 first、尾结点 last),
向其尾插一个新结点(nNode)。
创建新结点 nNode:前驱指向 last,后继为空。
思路一
先判断链表是否为空,再移动尾指针。
-
last != null(链表非空):将 last 后继指向 nNode,last 移动到 nNode。
-
last == null(链表为空):nNode 是唯一元素,first 和 last 指向 nNode。
相应代码
// 创建新节点
Node nNode = new Node(last, data, null);
// 判断链表是否为空(通过 last)
if (last == null) {
first = nNode;
} else {
last.next = nNode;
}
// 移动尾指针
last = nNode;
思路二
先移动尾指针,再判断链表是否为空。
-
使用临时指针(tmp)保存 last,将 last 移动到 nNode。
-
判断 tmp 是否为空:
-
tmp != null(链表非空):将 tmp 后继指向 nNode。
-
tmp == null(链表空):nNode 是唯一元素,first 指向 nNode。
-
相应代码
// 创建新节点
Node nNode = new Node(last, data, null);
// 保存并移动尾指针
Node tmp = last;
last = nNode;
// 判断链表是否为空(通过 tmp)
if (tmp == null) {
first = nNode;
} else {
tmp.next = nNode;
}
add()
可以看出,
add()
类似上述的思路二。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
分析:思路二的优势。
两种思路的主要区别在于,后者定义了局部变量(
tmp
)来保存last
。后续操作均基于
tmp
,而不是直接操作last
。
个人理解(基于 JVM 角度):提高性能。
- 成员变量 last 位于堆中,局部常量位于运行时常量池(类似本地缓存)。
- 相比从堆中读取数据,从常量池中读取数据的效率更高。