学透 LinkedList 底层实现原理,狂虐面试官!

前言:面试时被问到 LinkedList 的底层实现,瞬间哑口无言,所以决定狠狠地啃一下 Java源码,目标就是吊打面试官!
好了,废话不多说,进入正题。

简单介绍

很多人听到 LinkedList,应该就能猜到它的底层是用链表实现的,更准确来说,是用 双向链表 实现的,和常用的 ArrayList 不同,LinkedList 更适用于插入和删除操作较多的场景,而且LinkedList 实现了 Queue 接口,所以经常被当做队列来使用。

那么 LinkedList 底层到底是怎么实现的呢?

LinkedList 的成员变量

俗话说,学习一个类,就要先从这个类包含的属性入手,那么我们就来看看吧!

LinkedList 的主要变量:

transient int size = 0;		// 双向链表的节点数量
transient Node<E> first;	// 双向链表的第一个节点
transient Node<E> last;		// 双向链表的最后一个节点

另外,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;
	}
}

注意,看到这里我们就要思考一下,LinkedList 的主要操作是怎么实现的?
我们不妨来大胆猜想一下:

  • add(E e) :将指定元素添加到链表末尾。让链表的最后一个节点的 next 指向新增节点 e,让新增节点的 prev 指向最后一个节点,最后将 last 指向新增节点,size+1。
  • get(int index):返回链表中指定位置的元素。检查下标是否越界,如果越界,抛异常;没越界,则从头或者从尾部开始遍历(指定节点离哪个近就从哪开始),找到指定节点后返回节点值。
  • peek():检索但不删除此链表的第一个元素。若 first 为空,返回 null,否则返回 first 节点值。
  • poll() :检索并删除此链表的第一个元素。若 first 为空,返回 null,否则返回 first 节点值,并删除 frist 节点。
  • remove(Object o) :从链表中删除第一次出现的指定元素。若 o 为空,遍历链表找到值为 null 的节点并删除;否则,遍历链表找到值等于 o 的节点值 的节点,并删除。
  • size() :返回链表的元素数量。直接返回 size。

--------------------------------------------- 想狂虐面试官?请往下分析源码 ---------------------------------------------

LinkedList 的主要方法

接下来将分析 LinkedList 几个主要方法的源码,不要紧张,可以先放松一下,深吸一口气,其实阅读源码并没有那么难,只要掌握正确的方法和节奏,看我来简单地分析给你听:

1.add 方法

有多种不同的add()方法,我们这里分析一下比较常用的 add(E e):

注:代码中的中文注释是笔者添加的
public boolean add(E e) {
	// 将 e 添加到链表尾部
	linkLast(e);
	return true;
}

void linkLast(E e) {
	// 尾节点
	final Node<E> l = last;
	// 新增节点初始化,prev 指向 尾节点
	final Node<E> newNode = new Node<>(l, e, null);
	// last 指向 新增节点
	last = newNode;
	// 旧链表尾节点 为空
	if (l == null)
		first = newNode;
	// 不为空
	else
		l.next = newNode;
	// 链表节点数量 + 1
	size++;
	modCount++;
}

总结
调用 add(E e) 方法,先初始化该新增节点,并将 prev 指向旧链表的最后一个节点,若旧链表最后一个节点为空(即链表为空),first 和 last 都指向新增节点,最后让 size +1,链表操作次数 modCount+1。

2.get 方法

LinkedList 中的 get 方法为 get(int index) ,那我们就来看一下源码:

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) {
	// 下标离头节点比较近, 从头节点开始遍历
	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;
	}
}

总结
调用 get(int index) 方法,先判断下标 index 是否越界,越界则抛异常;没越界则根据下标选择从头节点或者从尾节点开始遍历,找到指定下标节点,则返回该节点的节点值。

3.poll 方法

peek() 方法和 poll() 方法都是检索链表的头节点,但是 poll() 检索后要删除该节点,比较复杂,所以我们只分析 poll()。

public E poll() {
	// 头节点为空,返回 null, 否则删除头节点并返回
	final Node<E> f = first;
	return (f == null) ? null : unlinkFirst(f);
}

// 删除头节点并返回
private E unlinkFirst(Node<E> f) {
	// 获取头节点元素值
	final E element = f.item;
	// 获取第二个节点
	final Node<E> next = f.next;
	// 头节点元素值 置空
	f.item = null;
	// 头节点 next 置空, 后期 JVM 会进行 GC 回收
	f.next = null; // help GC
	// first 指向 第二个节点
	first = next;
	// 第二个节点为空
	if (next == null)
		last = null;
	// 第二个节点不为空
	else
		next.prev = null;
	// 链表节点数量 + 1
	size--;
	modCount++;
	return element;
}

总结
调用 poll() 方法,先判断头节点是否为空,为空则返回 null;不为空则将 first 指向 第二个节点后,返回头节点的值,并删除头节点。

4.remove 方法

LinkedList 的 remove 方法也有很多个,我们来分析常用的 remove(Object o) 方法(删除第一次出现指定元素的节点)。

public boolean remove(Object o) {
	// 若 o 为空
	if (o == null) {
		// 遍历找到值为 null 的节点并删除
	    for (Node<E> x = first; x != null; x = x.next) {
	        if (x.item == null) {
	            unlink(x);
	            return true;
	        }
	    }
	// 若 o 不为空
	} else {
		// 遍历找到值为 x的值 的节点并删除
	    for (Node<E> x = first; x != null; x = x.next) {
	        if (o.equals(x.item)) {
	            unlink(x);
	            return true;
	        }
	    }
	}
	return false;
}

//删除 x 节点
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;
}

总结
调用 remove(Object o),若 o 为空,则遍历链表找到值为 null 的节点并删除;若 o 不为空,则遍历链表找到值为 o的值 的节点并删除。

分析结束啦!谢谢观看~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星空是梦想

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

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

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

打赏作者

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

抵扣说明:

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

余额充值