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底层源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot利于第三方服务进行ip定位获取省份城市

    Springboot利于第三方服务进行ip定位获取省份城市

    本文主要介绍了Springboot利于第三方服务进行ip定位获取省份城市,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 详解SpringBoot中异步请求的实现与并行执行

    详解SpringBoot中异步请求的实现与并行执行

    这篇文章主要为大家详细介绍了在SpringBoot中如何是实现异步请求、并行执行,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02
  • 深入解析@InitBinder注解的功能与应用

    深入解析@InitBinder注解的功能与应用

    这篇文章主要介绍了深入解析@InitBinder注解的功能与应用,从字面意思可以看出这个的作用是给Binder做初始化的,被此注解的方法可以对WebDataBinder初始化,webDataBinder是用于表单到方法的数据绑定的,需要的朋友可以参考下
    2023-10-10
  • springSecurity实现简单的登录功能

    springSecurity实现简单的登录功能

    这篇文章主要为大家详细介绍了springSecurity实现简单的登录功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • SpringBoot中的Condition包下常用条件依赖注解案例介绍

    SpringBoot中的Condition包下常用条件依赖注解案例介绍

    这篇文章主要介绍了SpringBoot中的Condition包下常用条件依赖注解案例,文章基于Java的相关资料展开主题详细内容,需要的小伙伴可以参考一下
    2022-04-04
  • 深入了解MyBatis参数

    深入了解MyBatis参数

    今天小编就为大家分享一篇关于深入了解MyBatis参数,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • Java特性之注解和异常 Throwable

    Java特性之注解和异常 Throwable

    这篇文章主要介绍了Java特性之注解和异常,注解是JDK1.5版本开始引入的一个特性,Throwable是Java语言中所有错误与异常的超类,文章围绕主题展开更多的相关介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • 如何用java给一个文件夹打成压缩包(附代码)

    如何用java给一个文件夹打成压缩包(附代码)

    项目中需要将文件夹打包成压缩包下载,所以下面这篇文章主要给大家介绍了关于如何用java给一个文件夹打成压缩包的相关资料,文中给出了详细的代码示例,需要的朋友可以参考下
    2023-10-10
  • Spring Security 登录时添加图形验证码实现实例

    Spring Security 登录时添加图形验证码实现实例

    这篇文章主要为大家介绍了Spring Security 登录时添加图形验证码实现实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Java中invokedynamic字节码指令问题

    Java中invokedynamic字节码指令问题

    这篇文章主要介绍了Java中invokedynamic字节码指令问题,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04

最新评论