LinkedList源码分析

今天来看一下 LinkedList 原理。本文解析的LinkedList源代码基于 JDK 1.8 。
LinkedList 是 Java 集合框架中一个重要的实现,其底层采用的双向链表结构。和 ArrayList 一样,LinkedList 也支持空值和重复值。由于 LinkedList 基于链表实现,存储元素过程中,无需像 ArrayList 那样进行扩容。但有得必有失,LinkedList 存储元素的节点需要额外的空间存储前驱和后继的引用。另一方面,LinkedList 在链表头部和尾部插入效率比较高,但在指定位置进行插入时,效率一般。原因是,在指定位置插入需要定位到该位置处的节点,此操作的时间复杂度为O(N)。最后,LinkedList 是非线程安全的集合类,并发环境下,多个线程同时操作 LinkedList,会引发不可预知的错误。

链表

在分析LinkedList之前我们先复习一下链表这种数据结构

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻 辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

链表按照指向可以分为单向链表跟双向链表

单向链表

单向链表.png

从head节点开始,next指针只想下一个节点的数据,tail节点的next指针指向null

双向链表

双向链表.png

每个节点有连个指针,pre跟next,除了head节点pre指针跟tail节点的next指针都指向null之外,其余的相邻节点的指针不管是从头到尾还是反过来,当前节点的两个指针包含了相邻节点的指向。

单向链表和双向链表

单向循环链表跟单向链表的区别在于,tail节点指向head节点的数据

继承体系

LinkedList 的继承体系较为复杂,继承自 AbstractSequentialList,同时又实现了 List 和 Deque 接口。继承体系图如下:


Linkedlist关系图.png

另外,LinkedList 还实现了 Deque (double ended queue),Deque 又继承自 Queue 接口。这样 LinkedList 就具备了队列的功能。

源码分析

Node

LinkedList 既然作为链表,那么肯定会有节点了,我们看下节点的定义:

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;
    }
}

每个节点都包含了前一个节点 prev 以及后一个节点 next ,item 就是要当前节点要存储的元素。

add(E e)

public boolean add(E e) {
    // 直接往队尾加元素
    linkLast(e);
    return true;
}

void linkLast(E e) {
    // 保存原来链表尾部节点,last 是全局变量,用来表示队尾元素
    final Node<E> l = last;
    // 为该元素 e 新建一个节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 将新节点设为队尾
    last = newNode;
    // 如果原来的队尾元素为空,那么说明原来的整个列表是空的,就把新节点赋值给头结点
    if (l == null)
        first = newNode;
    else
    // 原来尾结点的后面为新生成的结点
        l.next = newNode;
    // 节点数 +1
    size++;
    modCount++;
}

在 linkLast(E e) 中,先去判断了原来的尾节点是否为空。如果尾节点是空的,那么就说明原来的列表是空的。会将头节点也指向该元素;如果不为空,直接在后面追加即可。

其实在 first 之前,还有一个为 null 的 head 节点。head 节点的 next 才是 first 节点。

get(int index)

    checkElementIndex(index);
    return node(index).item;
}

在内部调用了 node(index) 方法,而 node(index) 方法在上面已经分析过了。就是判断在前半段还是在后半段,然后遍历得到即可。

remove(int index)

    checkElementIndex(index);
    return unlink(node(index));
}

remove(int index) 中调用了 unlink(Node<E> 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 {
        // 设置该节点的前节点的 next 为该节点的 next
        prev.next = next;
        x.prev = null;
    }
    // 如果要删除的是尾节点,那么设置尾节点为上一个节点
    if (next == null) {
        last = prev;
    } else {
        // 设置该节点的下一个节点的 prev 为该节点的 prev
        next.prev = prev;
        x.next = null;
    }
    // 设置 null 值,size--
    x.item = null;
    size--;
    modCount++;
    return element;
}

remove(Object o)

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

remove(Object o) 的代码就是遍历链表,然后得到相等的值就把它 unlink(x) 了。至于 unlink(Node<E> x) 的代码,上面已经分析过啦。

set(int index, E element)

public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    // 设置 x 节点的值为新值,然后返回旧值
    x.item = element;
    return oldVal;
}

clear()

public void clear() {
    // 遍历链表,然后一一删除置空
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

以上分析源码完了,下面再来对比下LinkedList和ArrayList

LinkedList和ArrayList的对比

1、顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置塞一个数据就好了;LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList

2、因为LinkedList里面不仅维护了待插入的元素,还维护了Entry的前置Entry和后继Entry,如果一个LinkedList中的Entry非常多,那么LinkedList将比ArrayList更耗费一些内存

3、数据遍历的速度,看最后一部分,这里就不细讲了,结论是:使用各自遍历效率最高的方式,ArrayList的遍历效率会比LinkedList的遍历效率高一些

4、有些说法认为LinkedList做插入和删除更快,这种说法其实是不准确的:

(1)LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址
(2)ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址

LinkedList 相对于 ArrayList 来说,源码会复杂一点。因为涉及到了链表,所以会有 prev 和 next 之分。但是静下心来阅读,还是可以看懂的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • 一.线性表 定义:零个或者多个元素的有限序列。也就是说它得满足以下几个条件:  ①该序列的数据元素是有限的。  ②...
    Geeks_Liu阅读 2,697评论 1 12
  • LinkedList 认识 LinkedList是一种双向链表,双向链表我认为有两点含义: 链表中任意一个存储单元...
    zlb阅读 240评论 0 0
  • 概述 在分析LinkedList的源码之前,先看一下ArrayList在数据结构中的位置,常见的数据结构按照逻辑结...
    wustor阅读 595评论 0 0
  • 才过两个月的时间,竟然想不起当初主编写作营是什么出现在我的眼前,只记得自己为什么要报名学习主编写作营的原因。那时候...
    大Amo阅读 327评论 5 2
  • 六一儿童节,K歌平台上一个歌友唱了首《童年的小摇车》,不记得多少年没听过这首歌了,听得我瞬间泪崩。透过泪光,...
    云彩Lyq阅读 575评论 1 0