LinkedList 源码学习
本次分析的代码版本是
JDK 7
一、定义
LinkedList
的内部实现是双向链表,元素和元素之间不是单独存放的,而是通过前节点和后节点连接起来。
节点是 LinkedList
中的一个内部类,具体定义如下。
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;
}
}
item
指向当前元素,next
指向后节点,prev
指向前节点。
LinkedList
内部主要通过以下三个变量来维护链表关系。
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
二、添加元素
下面是 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++;
}
代码的逻辑是这样的:
- 通过
Node
的构造器方法创建一个新的节点newNode
,将现在的最后一个元素l
作为新节点newNode
的前节点,当前元素就是自己e
,后节点为null
。 - 将尾节点
last
指向新节点newNode
。 - 如果原来的尾结点
l
是null
,说明添加的是第一个元素,将新节点作为头结点first
。如果尾结点l
不为null
,将原来的尾结点l
的后节点l.next
指向新节点newNode
。 - 容量
size
加 1,容器结构性变化次数modCount
加 1。
三、根据索引查找元素
下面是 get
方法的源码。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
- 先检查索引
index
是否合法,如果不合法就抛出索引越界异常IndexOutOfBoundsException
。 size >> 1
就是 size 除以 2,判断被查找元素的索引在前半部分还是在后半部分,如果在前半部分就从头查找,否则从尾部开始查找。
四、根据内容查找元素
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.判断元素是否为 null
,如果为 null
,则将找到的第一个为 null
的元素返回,否则使用 equals
方法从头到尾查询,将相等的第一个元素返回。
可以看出查找元素的性能是比较差的。
五、插入元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
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++;
}
- 检查索引是否合法,如果不合法就抛出索引越界异常
IndexOutOfBoundsException
。 - 如果
index
就是容量size
的大小,直接调用linkLast
在链表末尾添加元素。 - 如果
index
小于容量size
,就调用linkBefore
插入元素。 - 在
linkBefore
方法中,参数e
代表需要插入的元素,参数succ
代表需要插入的索引位置对应的元素。 - 新建一个节点
newNode
, 前节点为pred
,后节点为succ
。 - 让后节点的上一个节点
succ.prev
指向新节点newNode
。 - 让前节点的下一个节点指向新节点,如果前节点为空,让头节点
first
指向新节点newNode
。 - 容量
size
加 1,容器结构性变化次数modCount
加 1。
六、删除元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
删除主要调用了 unlink
方法,删除的主要思路就是让被删除元素 x
的前节点和后节点连接起来。
- 让
x
的前节点的下一个节点指向x
的后节点,如果x
的前节点为空,说明删除的是头节点,需要将x
的后节点作为头节点。 - 让
x
的后节点的上一个节点指向x
的前节点,如如果x
的后节点为空,说明删除的尾节点,需要将x
的前节点作为尾节点。 - 容量
size
减 1,容器结构性变化次数modCount
减 1。
(完)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于