死磕 java集合之LinkedList源码分析

简介

LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为队列或者栈来使用,它是怎么实现的呢?让我们一起来学习吧。

继承体系

死磕 java集合之LinkedList源码分析_第1张图片

通过继承体系,我们可以看到LinkedList不仅实现了List接口,还实现了Queue和Deque接口,所以它既能作为List使用,也能作为双端队列使用,当然也可以作为栈使用。

源码分析

主要属性

// 元素个数transientintsize=0;// 链表首节点transientNode first;// 链表尾节点transientNode last;

属性很简单,定义了元素个数size和链表的首尾节点。

主要内部类

典型的双链表结构。

privatestaticclassNode {    E item;    Nodenext;    Node prev;    Node(Node prev, E element, Nodenext) {this.item = element;this.next=next;this.prev = prev;    }}

主要构造方法

publicLinkedList(){}publicLinkedList(Collection c){this();    addAll(c);}

两个构造方法也很简单,可以看出是一个无界的队列。

添加元素

作为一个双端队列,添加元素主要有两种,一种是在队列尾部添加元素,一种是在队列首部添加元素,这两种形式在LinkedList中主要是通过下面两个方法来实现的。

// 从队列首添加元素privatevoidlinkFirst(E e){// 首节点finalNode f = first;// 创建新节点,新节点的next是首节点finalNode newNode =newNode<>(null, e, f);// 让新节点作为新的首节点first = newNode;// 判断是不是第一个添加的元素// 如果是就把last也置为新节点// 否则把原首节点的prev指针置为新节点if(f ==null)        last = newNode;elsef.prev = newNode;// 元素个数加1size++;// 修改次数加1,说明这是一个支持fail-fast的集合modCount++;}// 从队列尾添加元素voidlinkLast(E e){// 队列尾节点finalNode l = last;// 创建新节点,新节点的prev是尾节点finalNode newNode =newNode<>(l, e,null);// 让新节点成为新的尾节点last = newNode;// 判断是不是第一个添加的元素// 如果是就把first也置为新节点// 否则把原尾节点的next指针置为新节点if(l ==null)        first = newNode;elsel.next = newNode;// 元素个数加1size++;// 修改次数加1modCount++;}publicvoidaddFirst(E e){    linkFirst(e);}publicvoidaddLast(E e){    linkLast(e);}// 作为无界队列,添加元素总是会成功的publicbooleanofferFirst(E e){    addFirst(e);returntrue;}publicbooleanofferLast(E e){    addLast(e);returntrue;}

典型的双链表在首尾添加元素的方法,代码比较简单,这里不作详细描述了。

上面是作为双端队列来看,它的添加元素分为首尾添加元素,那么,作为List呢?

作为List,是要支持在中间添加元素的,主要是通过下面这个方法实现的。

// 在节点succ之前添加元素voidlinkBefore(E e, Node succ) {// succ是待添加节点的后继节点// 找到待添加节点的前置节点finalNode pred = succ.prev;// 在其前置节点和后继节点之间创建一个新节点finalNode newNode =newNode<>(pred, e, succ);// 修改后继节点的前置指针指向新节点succ.prev = newNode;// 判断前置节点是否为空// 如果为空,说明是第一个添加的元素,修改first指针// 否则修改前置节点的next为新节点if(pred ==null)        first = newNode;elsepred.next= newNode;// 修改元素个数size++;// 修改次数加1modCount++;}// 寻找index位置的节点Node node(intindex) {// 因为是双链表// 所以根据index是在前半段还是后半段决定从前遍历还是从后遍历// 这样index在后半段的时候可以少遍历一半的元素if(index < (size>>1)) {// 如果是在前半段// 就从前遍历Node x = first;for(inti =0; i < index; i++)            x = x.next;returnx;    }else{// 如果是在后半段// 就从后遍历Node x = last;for(inti =size-1; i > index; i--)            x = x.prev;returnx;    }}// 在指定index位置处添加元素publicvoidadd(intindex, E element) {// 判断是否越界checkPositionIndex(index);// 如果index是在队列尾节点之后的一个位置// 把新节点直接添加到尾节点之后// 否则调用linkBefore()方法在中间添加节点if(index ==size)        linkLast(element);elselinkBefore(element, node(index));}

在中间添加元素的方法也很简单,典型的双链表在中间添加元素的方法。

添加元素的三种方式大致如下图所示:

死磕 java集合之LinkedList源码分析_第2张图片

在队列首尾添加元素很高效,时间复杂度为O(1)。

在中间添加元素比较低效,首先要先找到插入位置的节点,再修改前后节点的指针,时间复杂度为O(n)。

删除元素

作为双端队列,删除元素也有两种方式,一种是队列首删除元素,一种是队列尾删除元素。

作为List,又要支持中间删除元素,所以删除元素一个有三个方法,分别如下。

// 删除首节点privateE unlinkFirst(Node f) {// 首节点的元素值finalE element = f.item;// 首节点的next指针finalNodenext= f.next;// 添加首节点的内容,协助GCf.item =null;    f.next=null;// help GC// 把首节点的next作为新的首节点first =next;// 如果只有一个元素,删除了,把last也置为空// 否则把next的前置指针置为空if(next==null)        last =null;elsenext.prev =null;// 元素个数减1size--;// 修改次数加1modCount++;// 返回删除的元素returnelement;}// 删除尾节点privateE unlinkLast(Node l) {// 尾节点的元素值finalE element = l.item;// 尾节点的前置指针finalNode prev = l.prev;// 清空尾节点的内容,协助GCl.item =null;    l.prev =null;// help GC// 让前置节点成为新的尾节点last = prev;// 如果只有一个元素,删除了把first置为空// 否则把前置节点的next置为空if(prev ==null)        first =null;elseprev.next=null;// 元素个数减1size--;// 修改次数加1modCount++;// 返回删除的元素returnelement;}// 删除指定节点xE unlink(Node x) {// x的元素值finalE element = x.item;// x的前置节点finalNodenext= x.next;// x的后置节点finalNode prev = x.prev;// 如果前置节点为空// 说明是首节点,让first指向x的后置节点// 否则修改前置节点的next为x的后置节点if(prev ==null) {        first =next;    }else{        prev.next=next;        x.prev =null;    }// 如果后置节点为空// 说明是尾节点,让last指向x的前置节点// 否则修改后置节点的prev为x的前置节点if(next==null) {        last = prev;    }else{next.prev = prev;        x.next=null;    }// 清空x的元素值,协助GCx.item =null;// 元素个数减1size--;// 修改次数加1modCount++;// 返回删除的元素returnelement;}// remove的时候如果没有元素抛出异常publicE removeFirst() {finalNode f = first;if(f ==null)thrownewNoSuchElementException();returnunlinkFirst(f);}// remove的时候如果没有元素抛出异常publicE removeLast() {finalNode l = last;if(l ==null)thrownewNoSuchElementException();returnunlinkLast(l);}// poll的时候如果没有元素返回nullpublicE pollFirst() {finalNode f = first;return(f ==null) ?null: unlinkFirst(f);}// poll的时候如果没有元素返回nullpublicE pollLast() {finalNode l = last;return(l ==null) ?null: unlinkLast(l);}// 删除中间节点publicE remove(intindex) {// 检查是否越界checkElementIndex(index);// 删除指定index位置的节点returnunlink(node(index));}

删除元素的三种方法都是典型的双链表删除元素的方法,大致流程如下图所示。

死磕 java集合之LinkedList源码分析_第3张图片

在队列首尾删除元素很高效,时间复杂度为O(1)。

在中间删除元素比较低效,首先要找到删除位置的节点,再修改前后指针,时间复杂度为O(n)。

前面我们说了,LinkedList是双端队列,还记得双端队列可以作为栈使用吗?

publicvoidpush(E e){    addFirst(e);}publicEpop(){returnremoveFirst();}

栈的特性是LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。

总结

(1)LinkedList是一个以双链表实现的List;

(2)LinkedList还是一个双端队列,具有队列、双端队列、栈的特性;

(3)LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1);

(4)LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n);

(5)LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效;

(6)LinkedList在功能上等于ArrayList + ArrayDeque;

进群:697699179可以获取Java、大数据各类入门学习资料!

这是我的微信公众号【编程study】各位大佬有空可以关注下,每天更新Java、大数据学习方法,感谢!

学习中遇到问题有不明白的地方,推荐加小编Java|大数据学习群:697699179内有视频教程 ,直播课程 ,等学习资料,期待你的加入

你可能感兴趣的:(死磕 java集合之LinkedList源码分析)