LinkedList集合是平时工作中常用的集合之一,它的增删操作效率高,但是相对它的查询效率就不如ArrayList集合了。对于想要了解ArrayList集合的朋友可以看我另外一篇博客ArrayList源码解析,本文只针对LinkedList集合的源码讲解分析。
LinkedList的基本信息属性
// 列表中的元素个数
transient int size = 0;
// 列表中第一个元素
transient Node<E> first;
// 列表中最后一个元素
transient Node<E> last;
// 记录列表内部结构改变次数
protected transient int modCount = 0;
Node内部类
private static class Node<E> {
E item; // 结点中的元素
// 以下两个属性说明,LinkedList的内部结构属于双向链表
Node<E> next; // 指向后继结点
Node<E> prev; // 指向前任结点
Node(Node<E> prev, E element, Node<E> next) { // 构造方法(指定节点内容、后继结点、前任结点)
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList类方法
对于LinkedList类中的方法,如果你熟悉链表这种数据结构的话,那么以下方法的源码对于你来说肯定十分简单。但是如果看完了以下方法的源码分析,还是觉得不能理解的话,那么我建议你接下来去了解下数据结构中的链表,之后再来看这篇博客。到那个时候相信这些代码对你来说不足为惧了。
- 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); // 创建结点,其内部元素为e,前任结点为last
last = newNode; // 将新添加的结点作为last末尾结点
if (l == null) // 若旧的末尾节点为空,说明此次添加的结点为列表的第一个结点
first = newNode; // 因此first结点也是新添加的节点
else
l.next = newNode; // 否则,旧的末尾节点的后继结点为新添加的结点
size++;
modCount++;
}
- add——在指定索引位置插入结点
public void add(int index, E element) {
checkPositionIndex(index); // 检查索引的合法性
if (index == size) // 指定索引等于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; // 指定索引大于0,而且小于列表结点数量,说明合法;否则非法
}
Node<E> node(int 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;
}
}
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev; // 获取插入指定位置结点的前任结点
final Node<E> newNode = new Node<>(pred, e, succ); // 创建新结点,其内部元素为e、前任结点为插入指定位置结点点的前任结点、后继结点为插入指定位置结点
succ.prev = newNode; // 插入指定位置结点的前任结点指向新加入的结点
if (pred == null) // 如果插入指定位置的节点的前任结点为空,说明插入位置为头结点
first = newNode; // 新加入的节点为头结点
else
pred.next = newNode; // 否则,插入指定位置结点的前任结点的后继结点指向新加入的节点
size++;
modCount++;
}
- addAll——将指定集合中的全部元素添加进列表
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c); // 在末尾添加结点
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 检查索引是否合法
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) { // 指定索引等于size,意味着在列表末尾添加结点
succ = null;
pred = last;
} else { // 否则,在指定位置添加结点
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); // 创建结点,其内部元素为e,前任结点为pred,后继结点为空
if (pred == null) // pred结点为空,说明列表为空,而newNode为第一个加入的结点
first = newNode; // 指定first结点
else // 否则,继续往插入位置添加结点
pred.next = newNode;
pred = newNode;
}
if (succ == null) { // succ为空,说明此次添加是在列表末尾添加结点,则重新指定末尾结点
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
- addFirst——添加头结点
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f); // 创建节点,其内部元素为e,前任结点为空,后继结点为头结点
first = newNode; // 指定新结点为头结点
if (f == null) // 头结点为空,说明列表为空,新节点为第一个插入的节点
last = newNode; // 同时指定为末尾节点
else
f.prev = newNode;
size++;
modCount++;
}
- addLast——添加末尾节点
public void addLast(E e) {
linkLast(e);
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null); // 创建新结点,其内部元素为e,前任结点为末尾节点,后继结点为空
last = newNode;
if (l == null) // 末尾结点为空,说明列表为空,新节点为第一个插入的节点
first = newNode; // 同时指定为末尾节点
else
l.next = newNode;
size++;
modCount++;
}
- contains——列表是否包含指定元素
public boolean contains(Object o) {
return indexOf(o) != -1;
}
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 { // 从头往后寻找,找到第一个与指定元素equals比较相等的元素,返回其所在位置的索引
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1; // 找不到,则返回-1
}
- remove——删除头结点
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null; // 头结点内部元素置空
f.next = null; // 头结点的前任结点置空
first = next; // 原第二结点作为头结点
if (next == null) // 原第二结点为空,说明列表只有一个头结点
last = null; // 移除后头结点后,列表为空,末尾节点指定为空
else
next.prev = null; // 否则,原第二结点的前任结点指定为空,与原头结点断绝关系
size--;
modCount++;
return element;
}
到此,LinkedList的源码解析就结束了。当然,LInkedList类中的方法不止如此,但是其他方法说到底也就是结点指针的变换,所以继续讲解其他方法也是炒冷饭而已,意义不大。而以上内容如果你都明白了,那么剩下的方法,直接去看源码也一定能够明白。