JDK17源码系列-LinkedList源码解读
1.LinkedList类图结构
2.存储结构
LinkedList与ArrayList存储结构不同,它是一个双向的链表,主要由其内部类Node维护链表,因此它不需要连续的存储空间,空间利用率较高
LinkedList类实现了List、Deque 、Cloneable、Serializable 接口
List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
Deque : 继承自 Queue 接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。
Cloneable :Cloneable 注解是一个标记接口,我们点进去会发现它并没有任何方法。此接口表明LinkedList具有拷贝能力,可以进行深拷贝或浅拷贝操作 。
3.两个重要属性:头结点与尾节点
java
transient Node<E> first; // 首节点
transient Node<E> last; // 尾节点
4.链表节点存储结构
java
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;
}
}
5.构造函数
- public LinkedList() 空构造函数
java
public LinkedList() {}
- public LinkedList(Collection<? extends E> c) 初始化一个包含指定集合的链表
java
public LinkedList(Collection<? extends E> c) {
this(); // 调用空的构造函数创建链表
addAll(c); // 将指定集合添加到空链表中
}
6.添加元素
- public boolean add(E e) 添加元素 ,默认使用尾插法
java
public boolean add(E e) {
linkLast(e);
return true;
}
- public void add(int index, E element) 在指定位置插入元素
java
public void add(int index, E element) {
checkPositionIndex(index); // 检查索引位置是否合法
if (index == size) // 如果索引位置为队尾,则尾插法
linkLast(element);
else
linkBefore(element, node(index)); // 否则使用头插法
}
- public void addFirst(E e) 添加头节点
java
public void addFirst(E e) {
linkFirst(e);
}
- public void addLast(E e) 插入尾节点
java
public void addLast(E e) {
linkLast(e);
}
- public boolean addAll(Collection<? extends E> c) 添加指定集合
java
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
- public boolean addAll(int index, Collection<? extends E> c) 在指定索引位置添加集合,并返回是否添加成功
java
checkPositionIndex(index); // 检查索引位置是否合法
Object[] a = c.toArray(); // 将指定集合c转换为数组
int numNew = a.length;
if (numNew == 0) // 如果指定集合为空,则返回false
return false;
Node<E> pred, succ;
if (index == size) { // 如果当前索引在尾节点位置,则将尾节点赋值给临时前向节点pred;
succ = null;
pred = last;
} else { // 否则获取index索引的节点并将该节点的前向节点赋值给临时前向节点pred
// 如果index为0,则succ为头结点,此时pred为空
succ = node(index);
pred = succ.prev;
}
// 遍历数组a(即遍历集合c)
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) // 如果临时前向节点pred为空, 则将新建节点作为头节点
first = newNode;
else
pred.next = newNode; 否则将临时前向节点pred的下个节点指向新建节点
pred = newNode; // 移动前向节点pred为新建节点
}
if (succ == null) { // 如果索引位置节点succ为空,则将尾节点赋值为临时的前向节点
last = pred;
} else {
pred.next = succ; // 否则pred的下个节点为索引位置节点succ
succ.prev = pred; // 索引位置节点succ的前向节点为pred
}
size += numNew;
modCount++;
return true;
}
- 获取元素
- public E get(int index) 根据索引值获取元素
java
public E get(int index) {
checkElementIndex(index); // 检查索引位置是否合法
return node(index).item; // 返回索引位置节点中存储的值
}
- public E getFirst() 获取列表首元素
java
public E getFirst() {
final Node<E> f = first; // 获取首节点
if (f == null)
throw new NoSuchElementException(); // 如果为空,则抛出异常
return f.item; // 返回首节点存储的值
}
- public E getLast() 获取尾节点
java
public E getLast() {
final Node<E> l = last; // 获取尾节点
if (l == null)
throw new NoSuchElementException(); //如果尾节点为空,则抛出异常
return l.item; // 获取尾节点存储的值
}
- 删除元素
- public boolean remove(Object o) 删除指定元素,并返回是否删除成功
java
public boolean remove(Object o) {
// 如果被删除的元素为空,遍历链表,找到存储值为空的节点,释放
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 如果删除元素不为空,则遍历链表找到存储元素与被删除元素相等的节点,释放
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
- public E removeFirst() 删除头结点
java
public E removeFirst() {
final Node<E> f = first;
if (f == null) // 如果头节点为空,抛出异常
throw new NoSuchElementException();
return unlinkFirst(f); //释放头节点
}
- public E removeLast() 删除尾节点
java
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException(); // 如果尾节点为空,则抛出异常
return unlinkLast(l); // 释放尾节点
}
public E set(int index, E element) 更新指定位置节点存储的,并返回该节点存储的原值
java
public E set(int index, E element) {
checkElementIndex(index); // 检查索引位置是否合法
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
- 节点的连接与释放操作
- private void linkFirst(E e) 连接头节点
java
// 头插法
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f); // 创建新节点
first = newNode; // 将原头结点赋值为新节点
if (f == null) // 如果原头节点为空,则表示空链表
last = newNode; // 将原尾节点指向新建节点
else
f.prev = newNode; // 将原头结点的前向指针指向新建节点
size++; // 链表元素个数+1
modCount++; // 链表修改次数+1
}
- void linkLast(E e) 连接尾节点
java
// 尾插法
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++; // 链表元素个数+1
modCount++; // 链表修改次数+1
}
- void linkBefore(E e, Node succ) 在指定节点前插入新节点
java
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; // 获取指定节点的前向节点
final Node<E> newNode = new Node<>(pred, e, succ); // 创建新的节点
succ.prev = newNode; // 将指定节点的前向节点指向新建节点
if (pred == null) // 如果原指定节点的前向节点为空,则将头节点指向新建节点
first = newNode;
else
pred.next = newNode; // 否则,将指定节点的前向节点的下个节点指向新建节点
size++;
modCount++;
}
- E unlink(Node x) 释放非空节点
java
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item; // 获取节点x的元素值
final Node<E> next = x.next; // 获取节点x的后向节点
final Node<E> prev = x.prev; // 获取节点x的前向节点
if (prev == null) { // 如果前向节点为空
first = next; // 则将链表的头节点设置为x的后向节点
} else {
// 如果x的前向节点不为空,则x的前向节点的后向节点指向x的后向节点
// x的前向节点设置为空
prev.next = next;
x.prev = null;
}
// 如果x的后向节点为空,则将链表的尾节点指向x的前向节点
if (next == null) {
last = prev;
} else {
// 否则 x的后向节点的前向节点指向x的前向节点,x的后向节点设置为空,即释放了x
next.prev = prev;
x.next = null;
}
x.item = null; //x存储的元素设置为空
size--;
modCount++;
return element;
}
- private E unlinkFirst(Node f) 释放链表的头节点
java
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item; // 获取头节点的值
final Node<E> next = f.next; 获取头节点的下个节点
f.item = null; // 将头节点的元素以及下个节点置空
f.next = null; // help GC
first = next; // 将链表的头节点设置为原头节点的下个节点
if (next == null) // 如果原链表的头节点下个节点为空(即链表中只存储一个节点),释放头节点后将尾节点置空
last = null;
else
next.prev = null;// 否则将原头节点的后向节点的前向节点置空
size--; // 链表大小-1
modCount++;
return element; // 返回被删除的头节点中存储的元素值
}
- private E unlinkLast(Node l) 释放尾节点
java
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item; // 获取尾节点存储的值
final Node<E> prev = l.prev; // 获取尾节点l的前向节点
l.item = null; // 将尾节点l存储的值以及前向节点置空
l.prev = null; // help GC
last = prev; // 将尾节点的前向节点设置为链表新的尾节点
if (prev == null) // 如果原尾节点l的前向节点为空(链表中仅存储一个节点),释放尾节点l后将头节点置空
first = null;
else
prev.next = null; // 如果原尾节点的前向节点不为空,则将其后向节点置空(因为它已经成为链表新的尾节点)
size--;
modCount++;
return element;
}
- 查找元素索引操作
- public int indexOf(Object o) 查找元素在链表中的位置
java
public int indexOf(Object o) {
int index = 0;
if (o == null) { // 如果查找元素为空,则遍历链表,查找存储元素为空的节点的索引位置
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
// 如果查找元素不为空,则遍历链表,查找存储值与查找值相等的节点的索引,并返回
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;// 如果没找到返回-1
}
- public int lastIndexOf(Object o) 查找指定元素在链表中最后一次出现的索引位置
java
public int lastIndexOf(Object o) {
int index = size; //将index设置为链表长度
// 如果查找元素为空,则倒序遍历链表。查找存储元素为空的节点的索引值
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
// 如果查找元素不为空,则倒序遍历链表。查找存储元素与查找元素想的节点的索引值
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1; // 如果没找到,则返回-1
}
- 队列的操作
- public E peek() 获取头节点存储的元素值
java
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item; // 如果头节点为空,则返回空,否则返回头节点存储的值
}
// 这个方法也是获取链表的头节点,不同的是如果链表为空,它会抛出NoSuchElementException异常
public E element() {
return getFirst();
}
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item; // 获取头结点元素值
}
- public E poll() 出队操作,并移动头节点位置
java
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f); // 返回链表头节点,并释放头节点
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f); // 从队头出队,并释放头节点
}
// 这个也是出队操作,并且也需要释放头节点
public E remove() {
return removeFirst();
}
- public E peekLast() 从队尾出队,不释放节点
java
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
// 从队尾出队,但是释放了链表节点
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
- public boolean offer(E e) 向队尾添加元素
java
public boolean offer(E e) {
return add(e);
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
- public boolean offerFirst(E e) 向队头插入元素
java
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
- public void push(E e) 入队操作
java
public void push(E e) {
addFirst(e);
}
- public E pop() 出队操作
java
public E pop() {
return removeFirst();
}
- 清空链表操作
- public void clear() 清空链表
java
// 清空每个节点,清空头尾节点
public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
- 链表转数组
- public Object[] toArray()
java
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
// 遍历链表,为数组赋值
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
- public T[] toArray(T[] a)
java
public <T> T[] toArray(T[] a) {
// 如果指定数组长度小于链表大小,则通过反射重新生成一个长度与链表大小一致的数组
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
// 遍历链表为数组元素赋值
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
// 如果指定数组长度大于链表大小,则将索引位置为size的数组元素置空
if (a.length > size)
a[size] = null;
return a;
}
- 链表序列化与反序列化
- private void writeObject(java.io.ObjectOutputStream s) 序列化调用
java
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size); // 将size序列化
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item); // 将每个节点序列话
}
示例:
生成了序列化文件
- private void readObject(java.io.ObjectInputStream s) 反序列化调用
java
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException,ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt(); // 反序列化链表大小
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject()); // 发序列化每个节点
}
示例:
打印反序列化结果