LinkedList源码导读

LinkedList和ArrayList都是使用频率非常高的列表。
LinkedList底层是一个双向链表的数据结构,它的插入和删除非常的快速,只需要修改对应节点的nextprev的指向即可,不像ArrayList需要大量的移动元素的位置。

LinkedList没有下标,不支持快速随机访问,因此没有实现java.util.RandomAccess接口,遍历列表时应该优先采用迭代器的方式,性能会更好一些,LinkedList用for循环便利是灾难!!!

但是LinkedList实现了java.util.Deque接口,因此它也是一个双向队列,支持pushpoppeekpool等操作。
在这里插入图片描述

属性

// 元素的大小
transient int size = 0;

// 指向链头的指针
transient Node<E> first;

// 指向链尾的指针
transient Node<E> last;

构造函数

LinkedList没有所谓的容量一说,它是无界的,只要有内存可用,就能新建节点通过next相连,因此它的构造函数比ArrayList少一些。

/**
 * 构造一个空的链表
 */
public LinkedList() {
}

/**
 * 给定一个集合,将所有元素都添加到链表中
 */
public LinkedList(Collection<? extends E> c) {
	this();
	addAll(c);
}

核心操作

LinkedList的源码相比ArrayList要简单,不管是插入还是删除,都不需要移动元素,只需要修改节点的指向即可,添加元素时,也没有扩容一说,直接新建节点通过指针连接即可,代码量要少的多。

add

add(E e)
添加元素到链尾:

  1. 新建节点newNode
  2. 前任last的next指向newNode
  3. newNode的prev指向前任last
  4. last指向newNode。
// 添加元素到链尾
public boolean add(E e) {
	linkLast(e);
	return true;
}

/*
将元素插入到链尾。
LinkedList的插入过程还是很简单的,修改节点指向即可,不存在扩容的问题。
 */
void linkLast(E e) {
	final Node<E> l = last;
	// 创建一个Node节点入链,当前节点的prev指向前任链尾
	final Node<E> newNode = new Node<>(l, e, null);
	last = newNode;
	// last为空,代表是第一次插入,则newNode即是链头,也是链尾。
	if (l == null)
		first = newNode;
	else
		// 前任链尾的next指向当前节点
		l.next = newNode;
	size++;
	modCount++;
}

add(int index, E element)
将元素插入到指定位置:

  1. 校验index的合法性
  2. 如果index==size,直接插入到链尾。
  3. 否则找到下标index对应的节点,插入到它的前面。
// 将元素插入到指定位置
public void add(int index, E element) {
	// 首先是检查index是否合法:index >= 0 && index <= size
	checkPositionIndex(index);
	// 如果index=size,则直接插入到链尾
	if (index == size)
		linkLast(element);
	else
		/*
		说明插入的是中间位置:
		1.找到第index元素
		2.创建一个Node节点,插入到它的前面
		 */
		linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
	if (!isPositionIndex(index))
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 校验index是否合法
private boolean isPositionIndex(int index) {
	return index >= 0 && index <= size;
}

node(int index)通过下标查找对应的节点,这里JDK做了个优化,如果index过半了,则从链尾开始查找,否则从链头开始查找:

/*
找到第index位置的元素
这里做了一个优化:
如果inex没有过半,则从链头开始查找。
如果index过半,则从链尾开始查找。
 */
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;
	}
}

找到index对应的节点后,插入到它的前面:

/*
将元素e插入到succ的前面。
1.新建一个Node节点,prev指向succ的prev,next指向succ。
2.succ的prev指向新建节点。
3.succ的prev的next指向新建节点。
 */
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)
		// succ的prev是null,说明succ是链头,现任链头指向新建节点
		first = newNode;
	else
		pred.next = newNode;
	size++;
	modCount++;
}

addAll(Collection<? extends E> c)
将给定集合内的元素添加到链表中:

// 将集合追加到链尾
public boolean addAll(Collection<? extends E> c) {
	return addAll(size, c);
}

// 将集合追加到index位置
public boolean addAll(int index, Collection<? extends E> c) {
	// 检查index是否合法
	checkPositionIndex(index);

	// 将集合转数组,为空则返回false
	Object[] a = c.toArray();
	int numNew = a.length;
	if (numNew == 0)
		return false;

	Node<E> pred, succ;
	if (index == size) {
		// 插入到链尾
		succ = null;
		pred = last;
	} else {
		// 找到第index位置的元素
		succ = node(index);
		pred = succ.prev;
	}

	// 循环新建节点插入
	for (Object o : a) {
		@SuppressWarnings("unchecked") E e = (E) o;
		Node<E> newNode = new Node<>(pred, e, null);
		if (pred == null)
			first = newNode;
		else
			pred.next = newNode;
		pred = newNode;
	}

	if (succ == null) {
		// 如果是追加在链尾,则需要将last指向集合的最后一个节点
		last = pred;
	} else {
		/*
		如果插入的是中间位置:
		1.将集合的最后一个节点的next指向原第index位置的元素。
		2.原第index位置的元素的prev指向集合的最后一个元素.
		 */
		pred.next = succ;
		succ.prev = pred;
	}

	size += numNew;
	modCount++;
	return true;
}

addFirst(E e)
由于LinkedList实现了Deque接口,因此它也是一个双向队列,支持从队头添加:

/*
由于LinkedList实现了Deque接口,因此它也是一个双向队列。
添加元素到链头。
 */
public void addFirst(E e) {
	linkFirst(e);
}

/*
将e插入到链头:
1.新建节点,next指向原first
2.first指向新建节点
3.原first的prev指向新建节点
 */
private void linkFirst(E e) {
	final Node<E> f = first;
	final Node<E> newNode = new Node<>(null, e, f);
	first = newNode;
	if (f == null)
		// 说明原本链表是空的,e是第一个元素,last也要指向它
		last = newNode;
	else
		f.prev = newNode;
	size++;
	modCount++;
}

addLast(E e)
既然是双向队列,那肯定也支持队尾添加了,和add()是一样的。

/*
由于LinkedList实现了Deque接口,因此它也是一个双向队列。
添加元素到链尾,和add(E e)一样。
 */
public void addLast(E e) {
	linkLast(e);
}

get

LinkedList没有索引,因此顺序访问性能还不错,但是随机访问性能就很差了。

get(int index)
获取第index位置的元素,node()方法代码贴在前面了,过半从链尾开始查找,否则从链头开始查找,最大的查找次数不会超过一半。

/*
获取第index位置的元素:
LinkedList基于链表,没有下标,因此只能:
1.从first开始,通过next从头向尾找。
2.从last开始,通过prev从尾向头找。
 */
public E get(int index) {
	// 校验index合法性:index >= 0 && index < size
	checkElementIndex(index);
	/*
	查找第index位置的元素,做了优化:
	1.index过半,从链尾向链头查找。
	2.index没过半,从链头向链尾查找。
	 */
	return node(index).item;
}

getFirst()和getLast()
由于也是双向队列,因此支持快速访问队头和队尾元素:

/*
由于LinkedList实现了Deque接口,因此它也是一个双向队列。
获取链头元素。
 */
public E getFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return f.item;
}

/*
由于LinkedList实现了Deque接口,因此它也是一个双向队列。
获取链尾元素。
 */
public E getLast() {
	final Node<E> l = last;
	if (l == null)
		throw new NoSuchElementException();
	return l.item;
}

indexOf

indexOf(Object o)
从链头开始查找,获取第一个匹配项的index:

/*
从链头开始查找,获取第一个匹配项的index。
for循环存在重复代码,将o的非空判断外提了,避免重复判断,适当的重复是有必要的。
1.为什么不直接用:x.item.equals(o)???
	因为LinkedList允许存入null。
 */
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;
}

remove

remove()
移除链头元素,并返回:

/*
移除链头元素,并返回
 */
public E remove() {
	return removeFirst();
}

/*
移除并返回链头元素
 */
public E removeFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return unlinkFirst(f);
}

/*
移除链头元素并返回:
1.first指向原链头的next。
2.新first的prev置空。
 */
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也要置空。
		last = null;
	else
		// 新first的prev置空
		next.prev = null;
	size--;
	modCount++;
	return element;
}

remove(int index)
移除指定位置的元素并返回:

/*
移除指定位置的元素并返回:
1.通过node(index)找到需要移除的元素x。
2.x的next的prev指向x的prev。
3.x的prev的next指向x的next。
 */
public E remove(int index) {
	// 检查index的合法性
	checkElementIndex(index);
	return unlink(node(index));
}

/*
移除元素x:
1.x的next的prev指向x的prev。
2.x的prev的next指向x的next。
 */
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) {
		// x是链头,x移除后,next就是链头了。
		first = next;
	} else {
		prev.next = next;
		x.prev = null;
	}

	if (next == null) {
		// x是链尾,x移除后,prev就是链尾了。
		last = prev;
	} else {
		next.prev = prev;
		x.next = null;
	}

	x.item = null;//置空,帮助GC
	size--;
	modCount++;
	return element;
}

peek

peek()和peekFirst()
来自Deque,偷看一眼链头的元素,但不移除:

/*
来自Deque
获取链头元素,但不移除。
 */
public E peek() {
	final Node<E> f = first;
	return (f == null) ? null : f.item;
}

peekLast()
来自Deque,偷看一眼链尾的元素,但不移除:

/*
来自Deque
获取链尾元素,但不移除。
 */
public E peekLast() {
	final Node<E> l = last;
	return (l == null) ? null : l.item;
}

poll

来自Deque,获取链头或链尾的元素,并将其移除:

/*
来自Deque
返回链头元素,并将其移除。
 */
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);
}

/*
来自Deque
返回链尾元素,并将其移除。
 */
public E pollLast() {
	final Node<E> l = last;
	return (l == null) ? null : unlinkLast(l);
}

offer

来自Deque,向链表的链头或链尾处塞入元素,和addFirstaddLast一样,代码就不重复贴了。

push

来自Deque,向链尾处添加元素,和addFirst一样,代码就不重复贴了。

pop

来自Deque,弹出链头的元素,调用的还是unlinkFirst

/*
移除并返回链头元素
 */
public E removeFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return unlinkFirst(f);
}

总结

LinkedList整体代码还是比较简单的,它的操作没有ArrayList那么复杂,阅读起来比较轻松。
LinkedList它既是一个双向链表,又是一个双端队列,顺序访问效率高,随机访问效率低,如果要对LinkedList进行遍历,应该使用迭代器来遍历,for循环遍历是灾难!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小潘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值