前言
将常用的步骤解释的较为详尽易懂,若有错误麻烦指正。个人认为甚至些许啰嗦,建议有基础的同学直接看源码,源码更为直接了当易懂,且不会产生语句上的歧义。
继承关系
UML 图示

- 实现 List 接口,属于列表类
- 实现 RandomAccess 接口,具备快速随机访问的能力。(所有的 list 都可以支持随机访问,但是不一定支持快速随机访问,有的可以通过迭代器,如 linkedList,与底层实现有关)
- 实现 Cloneable 接口,可以重写 Object.clone() 方法,具有深度克隆的能力。
成员变量
/**
* 默认的初始化容量,如果构造方法未传入初始容量,则用 10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 构造方法传入初始容量为 0 时使用的空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 构造方法未传入初始容量,使用默认容量时的空数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 数据元素真正存储数组位置的指针
*/
transient Object[] elementData;
/**
* ArrayList 的大小,即存储元素的数量,与容量区分开
*/
private int size;
/**
* 数组分配的最大大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 从父类继承的,记录列表在结构上被修改的次数,add or remove
*/
protected transient int modCount = 0;
构造方法
无参构造,未指定初始化容量,预备一个默认容量的空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
传入初始化容量
- 大于 0,新建一个该容量大小的数组
- 等于 0,预备一个空数组(与未指定初始化容量提供的空数组区分开了)
- 小于 0,异常抛出,提供了非法参数
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);
}
}
传入一个集合,将其转化成数组
- 集合为空,使用预备的空数组
- 集合不为空,将其转化为 Object[]
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// toArray 返回值可能不是 Object[],新建一个 Object[],将其存入
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加
添加到列表末尾
添加到列表末尾,返回是否成功
public boolean add(E e) {
// 去确保列表能够支持再添加一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
确保容量足够,传入当前需要的最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
计算实际需要的容量
- 当前使用的是默认容量,与当前需要的最小容量相比,返回大的那个
- 如果不是,返回当前需要的最小容量
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) {
// 记录下,列表结构即将被修改一次
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容函数,传入实际需要的容量。
扩容到原数组长度的 1.5 倍,如果扩容 1.5 倍还不够(addAll 场景下),就扩容到需要的容量。新建一个数组,将原数组复制过去。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 如果 oldCapacity = 10 = 1010(二进制)
// oldCapacity >> 1 = 0101 = 5
// 扩容 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
添加到固定位置
public void add(int index, E element) {
// 检查是否数组越界
rangeCheckForAdd(index);
// 如上文解析
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将 index 位置及之后元素,向后挪一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 当前元素放入
elementData[index] = element;
size++;
}
检查是否数组越界
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
System.arraycopy()
/**
* 数据源数组
* copy 开始的起始索引
* 目标数组
* copy 到的起始索引
* copy 的长度
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
添加一个集合
// 添加一个集合
public boolean addAll(Collection<? extends E> c)
// 向某个索引开始,插入一个集合
public boolean addAll(int index, Collection<? extends E> c)
这两个方法与添加一个元素相差无几,只是数量上的改变,可自行参阅源码
删除
删除一个元素
区分删除的元素是否为 null,防止在比较的过程中报空指针异常
public boolean remove(Object o) {
if (o == 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) {
// 即将改变列表的结构
modCount++;
// 计算需要移动的长度,即当前索引后面的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
// 当前索引后面的元素,统统向前挪一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 末尾置为空,方便 gc
elementData[--size] = null; // clear to let GC do its work
}
删除部分元素
从 fromIndex 到 toIndex(不包括 to 本身)
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
// to 索引及后面的元素覆盖到 from 索引及之后
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// 置为 null,方便 gc
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
查看
public E get(int index) {
// 是否数组越界
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
修改
返回旧数据
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
LinkedList 和 ArrayList 的区别
- 底层数据结构的区别,一个基于动态数组,一个基于链表。
- 上述区别使得随机访问元素的形式不同,ArrayList 可以通过数组下标随机访问数组元素,可以实现 RandomAccess 接口;LinkedList 只能使用迭代器进行访问。
- 新增和删除元素,ArrayList 可能需要扩容,LinkedList 不需要。
- 空间浪费的体现不同,ArrayList 需要留足够的容量方便插入,LinkedList 需要维持链表间的关系和对象开销。

本文详细解析LinkedList的源码,涵盖继承关系、成员变量、构造方法、添加与删除操作,以及与ArrayList的区别。重点讲解了链表结构的优势和不足,适合进阶学习者深入理解数据结构实现。
314

被折叠的 条评论
为什么被折叠?



