LinkedList

本文详细解析了LinkedList的内部结构,包括其双向链表实现、节点构造、以及add、linkLast、linkBefore等关键方法的工作原理。同时,介绍了LinkedList如何通过实现Deque接口提供双端队列功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • LinkedList是实现了List和Deque接口的双向链表。实现了所有的可选列表操作(add, remove, set, get, size, indexOf, contains …),允许所有的元素,包括null。
  • 对链表的索引操作将从链表头或尾进行遍历,以较接近索引为准。
  • LinkedList是线程不安全的,根据需要可以使用集合类的相应方法包装(底层使用synchronized关键字);listIterator和iterator方法返回的迭代器具有fast-fail机制。这两点和ArrayList差不多。

1. 继承关系

public class LinkedList<E>
		extends AbstractSequentialList<E>
		implements List<E>, Deque<E>, Cloneable, java.io.Serializable

2. 变量

transient int size = 0;

/**
 * 链表第一个节点
 * 始终存在: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * 链表最后一个节点
 * 始终存在: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

Node为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;
	}
}

可以看到Node的实现非常简单,万物皆对象,一个链表结构就这么抽象出来了。(ArrayList底层维护的是一个数组,那LinkedList底层维护的就是一个个节点Node)
再回到定义的first和last这两个属性,根据它们始终存在的关系,可以得出两条规律:

  • 当LinkedList的size == 0,有first == null && last == null。
  • 当LinkedList的size >= 1,有first.prev == null && last.next == null。作为特例,当size == 1,有first == last。
    我们将在后面具体方法实现中看到这种逻辑。

3. 方法

此处仅选取一个具有代表性的方法进行分析,想要搞清楚更多细节,除了亲自阅读源码,别无他法。

  • void add(int index, E element)
/**
 * 在列表的指定位置插入指定元素。
 * 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加1)。
 *
 */
public void add(int index, E element) {
	checkPositionIndex(index);	// 需满足:index <= size。

	if (index == size)
		linkLast(element);		// 此时大致相当于add()方法,在链表末端添加元素。
	else
		linkBefore(element, node(index));
}

/**
 * 返回指定索引的节点(非空)
 */
Node<E> node(int index) {
	// assert isElementIndex(index);

	// 此处即LinkedList做为双向链表的一个体现。
	// 如果index小于size/2,就从链表头开始遍历,否则就从链表尾进行遍历。
	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;
	}
}

/**
 * 在链表末端添加元素e
 */
void linkLast(E e) {
	final Node<E> l = last;
	final Node<E> newNode = new Node<>(l, e, null);	// 新建节点newNode保存元素e,其前一节点为last,后一节点为null。
	last = newNode;
	// 如果添加元素之前last为null(根据始终存在,first也为null)。
	// 那么添加元素后链表只有一个节点,它既代表last又代表first。
	if (l == null)	
		first = newNode;
	else
		l.next = newNode;
	size++;
	modCount++;
}

/**
 * 在非空节点succ之前插入元素e
 */
void linkBefore(E e, Node<E> succ) {
	// assert succ != null;
	// 先记录succ的前节点prev,然后在succ和prev之间构造一个新节点newNode。
	// 然后再处理succ和prev这两个节点前后的连接关系。(即断开链表,再建立新的连接关系)
	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++;
}

类似linkLast和linkBefore的方法,还有:linkFirst、unlinkFirst、unlinkLast、unlink(访问类型为default或者private),它们封装了对链表的基本操作,方便其他方法直接调用。
关键是理解Node的抽象表达,能在脑海中建立这样一条双向链表的模型。
另外,LinkedList还实现了Deque接口。提供了peek、poll、offer、push、pop等方法(具体实现也是调用之前的linkLast之类的方法)。有了这些方法,就可以对LinkedList建立另外一种抽象,即双端队列。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值