Java中的LinkedList底层源码分析

 更新时间:2023年12月15日 09:19:37   作者:爱喝咖啡的程序员  
这篇文章主要介绍了Java中的LinkedList底层源码分析,底层基于双向链表,往LinkedList中间插入元素时,不需要移动大量的元素,只需要修改前后节点的指针,速度快,需要的朋友可以参考下

一. 基本原理和优缺点

优点:

1.底层基于双向链表,往LinkedList中间插入元素时,不需要移动大量的元素,只需要修改前后节点的指针,速度快。

2.适合频繁、大量的插入元素,不会导致频繁的扩容和拷贝元素。插入元素,只不过就是把新的元素挂到旧元素下面。

缺点:

1.不适合读取随机位置的元素,比如list.get(10),因为需要遍历链表,直到找到这个位置上的元素为止。

二. 源码分析

2.1 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);
	last = newNode;
	if (l == null)
		first = newNode;
	else
		l.next = newNode;
	size++;
	modCount++;
}

2.2 node

在双向链表中,找到目标下标对应的节点。这里可以学习链表的遍历方式。

Node<E> node(int index) {
	// assert isElementIndex(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 < (size >> 1),判断待寻找的节点在双向链表的前半部分,还是后半部分。

如果是前半部分,则会从双向链表的头节点开始遍历,通过节点的next指针,不断的向后寻找节点。→

如果是后半部分,则会从双向链表的尾节点开始遍历,通过节点的prev指针,不断的向前寻找节点。←

2.3 add(int index, E element)

在指定元素的前面,插入一个元素。比如现在想要在队尾插入一个元素。

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)
		first = newNode;
	else
		pred.next = newNode;
	size++;
	modCount++;
}

succ指向队尾元素,succ.prev表示队尾节点前面的那个节点(倒数第二个节点)。

首先,创建一个新节点,让新节点的prev指针指向倒数第二个节点,新节点的next指针指向队尾节点。

接着,让队尾节点指的prev指向新节点。

最后,把倒数第二个节点的next指针指向新节点。

2.4 get

获取一个随机位置的节点中的值。

public E get(int index) {
	checkElementIndex(index);
	return node(index).item;
}

随机读取是ArrayList的强项, 因为ArrayList底层基于数组实现,通过下标能快速找到对应的内存地址,接着直接读取内存地址中的值。

随机读取是LinkedList弱项,LinkedList底层基于双向链表实现,它没办法通过下标,直接找到内存地址,必须从头、尾节点开始,借助节点的prev和next指针,不断的向前或向后寻找,直到找到元素为止。

node()方法的代码之前已经学习过了,先大致的判断目标节点距离头、尾节点,哪个节点更近,尽量的减少查询的次数,接着就是借助头尾节点,不断的向前或向后找,比如从头节点,不断的向后找。

2.5 getFirst

返回头节点的值。如果头节点为空,则抛出异常。

public E getFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return f.item;
}

2.6 peek

返回头节点的值,头节点不需要出队。

peek与getFirst的区别:如果不存在头节点,peek会返回null,而getFirst会直接报错。

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

2.7 getLast

返回尾部节点的值。

public E getLast() {
	final Node<E> l = last;
	if (l == null)
		throw new NoSuchElementException();
	return l.item;
}

2.8 removeLast

删除队尾节点。

public E removeLast() {
	final Node<E> l = last;
	if (l == null)
		throw new NoSuchElementException();
	return unlinkLast(l);
}
return xprivate E unlinkLast(Node<E> l) {
	// assert l == last && l != null;
	final E element = l.item;
	final Node<E> prev = l.prev;
	l.item = null;
	l.prev = null; // help GC
	last = prev;
	if (prev == null)
		first = null;
	else
		prev.next = null;
	size--;
	modCount++;
	return element;
};

通过last指针找到队尾元素,断开队尾元素与倒数第二个元素的指针指向,last指针指向倒数第二个元素,作为新的队尾元素。

此时,原队尾节点的next指针等于null,item等于null,prev等于null,且没有被任何节点指向,接着就靠JVM来进行垃圾回收了。

2.9 removeFirst

删除队头节点。

public E removeFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return unlinkFirst(f);
}
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 = null;
	else
		next.prev = null;
	size--;
	modCount++;
	return element;
}

把队列的头节点与第二个节点之前的指针指向全部断开,让JVM来回收头节点。

接着,让first指针原队列的第二个节点,作为队列新的头结点。

2.10 remove(int index)

删除指定下标的节点。

public E remove(int index) {
	checkElementIndex(index);
	return unlink(node(index));
}
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;
    }

首先,通过node方法,遍历链表,找到待删除的节点。

接着,解除待删除节点,对于左右两边节点的所有指针指向。让左右两边的节点的next和prev指针互相指向。

最后,由JVM回收这个没有任何人指向的节点。

三. 总结

LinkedList是一个基于双向链表实现的数据结构,对于队头和队尾节点来说,无论是插入、删除还是读取节点的值,其实都是很轻松的。并且,默认从队尾插入节点,从队头获取节点,所以LinkedList天然就可以作为队列来使用。

由于基于双向链表实现,所以无论你怎么插入数据,LinkedList的性能都很不错,不用担心扩容,移动大量元素等问题,性能上很好。

但是呢,在链表的中间插入元素,比在队头和队尾插入元素的性能要差一些,这是因为队头和队尾分别有first和last指针指向着它们,如果要在链表的中间指定位置插入元素,首先要遍历链表,找到目标元素,然后才能修改左右两边节点的指针,插入节点。

此外,如果要随机获取某个位置的元素,尤其是链表内节点的数量很多的时候,由于需要遍历链表,所以性能比较差。

到此这篇关于Java中的LinkedList底层源码分析的文章就介绍到这了,更多相关LinkedList底层源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java解决同时出库入库订单号自动获取问题解决

    Java解决同时出库入库订单号自动获取问题解决

    在Java中,处理多线程环境下的订单号生成问题可以采用多种策略,如使用AtomicLong保证线程安全,通过定义订单号生成器并利用线程模拟出库和入库操作,每个线程从订单号生成器中获取唯一订单号,感兴趣的朋友一起看看吧
    2024-09-09
  • springboot:接收date类型的参数方式

    springboot:接收date类型的参数方式

    这篇文章主要介绍了springboot:接收date类型的参数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • java递归实现汉诺塔步骤介绍

    java递归实现汉诺塔步骤介绍

    大家好,本篇文章主要讲的是java递归实现汉诺塔步骤介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2022-01-01
  • java中的值传递和引用传递的区别分析

    java中的值传递和引用传递的区别分析

    本文介绍了“java中的值传递和引用传递的区别分析”,需要的朋友可以参考一下
    2013-03-03
  • POI读取excel简介_动力节点Java学院整理

    POI读取excel简介_动力节点Java学院整理

    这篇文章主要介绍了POI读取excel简介,详细的介绍了什么是Apache POI和组件,有兴趣的可以了解了解一下
    2017-08-08
  • Java实战网上电子书城的实现流程

    Java实战网上电子书城的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+JSP+maven+Mysql实现一个网上电子书城,大家可以在过程中查缺补漏,提升水平
    2022-01-01
  • MyBatis如何使用(一)

    MyBatis如何使用(一)

    这篇文章主要介绍了MyBatis如何使用(一)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • Spring Security自定义AuthenticationManager实现手机号/密码双认证

    Spring Security自定义AuthenticationManager实现手机号/密码双认证

    这篇文章给大家介绍了Spring Security自定义AuthenticationManager实现手机号/密码双认证,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2026-06-06
  • SpringBoot Maven打包如何根据环境排除文件

    SpringBoot Maven打包如何根据环境排除文件

    文章介绍了在SpringBoot项目中,根据不同的环境(开发、测试、生产)进行JSP文件打包处理的方法,通过配置`pom.xml`文件中的``标签,可以实现开发环境保留`index.jsp`文件,测试环境和生产环境排除该文件
    2024-12-12
  • Java反射机制详解

    Java反射机制详解

    这篇文章主要介绍了Java反射机制,首先简单介绍了反射机制的预备知识,进一步分析了Java反射机制的原理、实现技巧与应用方法,需要的朋友可以参考下
    2015-12-12

最新评论