本篇文章为大家进行Java中的LinkedList源码解析。在进行正式解析前先给大家介绍下线性表的概念。
线性表:线性表是最常用、最简单的一种数据结构,简言之,线性表是n个数据元素的有限序列。线性表中的每个数据元素最多只能又一个直接前趋元素,每个数据元素只能有一个直接后继元素;只有第一个元素没有直接前趋元素,而最后一个数据元素没后直接后继元素。线性表中的数据元素个数是该线性表的长度。
线性表的结构分类:线性表分为顺序存储(比如Java中的ArrayList)和非顺序存储(比如Java中的LinkedList)。
两种分类的优劣:线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此随机存取元素时比较简单,但是这个特点也使得在插入和删除元素时,造成大量的数据元素移动,同时如果使用静态分配存储单元,还要预先占用连续的储存空间,可能造成空间的浪费或者空间的溢出。如果采用链式存储,就不要求逻辑上相邻的数据元素在物理位置上也相邻,因此它没有顺序结构的所具有的缺点,但同时也失去了可随机存取的优点。
线性表与LinkedList之间的关系:Java的LinkedList实现所用的数据结构即为线性表的链式存储,而且是双向链表。
本位针对LinkedList的一些方法进行解析。
众所周知,链表是有一个个的节点组成的,LinkedList同理。
1.Node<E>
在LinkedList中Node充当节点的角色,它的结构如下:
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;
}
}
1.LinkedList.add(E);
/**
* 将指定的元素追加到此列表的末尾
*
* <p>这个方法等价于 {@link #addLast}.
*
* @param e 元素添加到此列表中
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 链接e作为最后一个元素。
*/
void linkLast(E e) {
//获取当前链表最后一个节点
final Node<E> l = last;
//创建一个新的节点 并且前趋节点为l,值为e,后继节点为空
final Node<E> newNode = new Node<>(l, e, null);
//将last也就是链表的最后一个节点赋值为新添加的节点
last = newNode;
//如果链表最后一个节点为空
if (l == null)
//那么表示链表原本是空的,将链表的第一个节点也赋值为新添加的节点
first = newNode;
else
//反正则将最后一个节点的后继赋值为新添加的节点
l.next = newNode;
//线性表长度加一
size++;
//链表操作次数加一
modCount++;
}
2.LinkedList.add(index,E);
/**
* 将指定元素插入到列表中的指定位置。
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到它们的索引中)。
*
* @param 要插入指定元素的索引
* @param 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//验证index是否合理 由源码可见 index必须是大于0并且小于当前链表的实际长度
checkPositionIndex(index);
//如果index等于链表的实际长度 则把元素插入到链表尾部
if (index == size)
linkLast(element);
//反之则插入到特定位置
else
linkBefore(element, node(index));
}
//验证index合理性的具体实现
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* 链接e作为最后一个元素。
*/
void linkLast(E e) {
//获取当前链表最后一个节点
final Node<E> l = last;
//创建新的节点 前趋节点为当前链表的最后一个节点,值为e,后继节点为空
final Node<E> newNode = new Node<>(l, e, null);
//将链表最后一个节点赋值为新添加的节点
last = newNode;
//如果当前链表的最后一个节点为空
if (l == null)
//则将当前链表的第一个节点也赋值为新添加的节点
first = newNode;
else
//反之将当前链表的后继节点改为新添加的节点
l.next = newNode;
//链表的实际长度加一
size++;
//链表的操作次数加一
modCount++;
}
/**
* 返回指定元素索引处的(非空)节点。
*/
Node<E> node(int index) {
// 确保index的存在 isElementIndex(index);
//如果index小于size >> 1(注:按位右移运算符。左操作数按位右移右操作数指定的位数。
是关于二进制的计算,不了解的可以去查API或者百度...)
//此处使用二分算法 大大提高执行效率 此判断的方法意思就是如果index在链表的前半部分就执行以下,index在链表的后半部分就执行else部分。
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;
}
}
/**
* 在非空节点succ之前插入元素e。
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null; 确保succ不为空!
//获取succ的前趋节点
final Node<E> pred = succ.prev;
//创建新的节点 前趋为succ的前趋节点,值为e,后继为succ
final Node<E> newNode = new Node<>(pred, e, succ);
将succ的前趋节点设置为新添加的节点
succ.prev = newNode;
//如果succ的前趋节点为空 表示succ为链表的第一个节点
if (pred == null)
//则将链表的第一个节点设置为新添加的节点
first = newNode;
else
//反正则将succ的前趋节点的 后继节点 设置为新添加的节点
pred.next = newNode;
//链表的真实长度 加一
size++;
//链表的操作次数加一
modCount++;
}
3.LinkedList.remove(index);
/**
* 删除列表中指定位置的元素。
* 将任何后续元素向左移动(从它们的索引中减去1)。
* 返回从列表中删除的元素。
*
* @param index 索引要删除的元素的索引
* @return 先前位于指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//验证index是否合理。由源码可见,index必须大于等于0并且小于当前链表的实际长度
checkElementIndex(index);
//unlink方法所需参数为一个节点 节点通过node传入index获取 node方法见以上方法描述,此 处不详细描述
return unlink(node(index));
}
//验证index是否合理的具体实现
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 指示该参数是否为现有元素的索引。
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null; 确保要删除的节点不为空,其中x代表要删除的节点
//x节点的值
final E element = x.item;
//x节点的后继节点
final Node<E> next = x.next;
//x节点的前趋节点
final Node<E> prev = x.prev;
//如果x节点的前趋节点为空 代表x节点为链表的第一个节点
if (prev == null) {
//将链表的第一个节点赋值为x节点的后继节点
first = next;
} else {
//将x节点的前趋节点 的 后继节点 设置为x的后继节点
prev.next = next;
//将x节点的前趋节点 设置为空
x.prev = null;
}
//如果x没有后继节点 代表x节点为链表的最后一个节点
if (next == null) {
//则将链表的最后一个节点设置为x节点的前趋节点
last = prev;
} else {
//反正,将x节点的 后继节点 的 前趋节点 赋值为x节点的前趋节点
next.prev = prev;
//将x节点的后继节点设置为空
x.next = null;
}
//将x节点的值设置为空
x.item = null;
//将链表的长度减一
size--;
//将链表的操作次数加一
modCount++;
//将删除节点的值返回
return element;
}
4.LinkedList.remove(Object);
/**
* 如果指定元素出现,则从该列表中删除它的第一个匹配项
* 如果此列表不包含该元素,则更正式地保持不变,删除索引最低的元素(相当于调用remove(),
* 删除链表第一个数据元素)
* {@code i} such that
* {@code Objects.equals(o, get(i))}
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
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 {
//Object不为空的情况 则循环获取到值为object节点,调用unlink删除
//unlink方法在remove(index)中有详细描述,此处不做解释
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}