LinkedList集合分析

前言

说到集合,Java中所有集合的总接口时Collection,Collection规范了集合基本的操作方法,同时约束了所有集合都存在泛型约束。下面是集合的组织架构:

interface Collection<E>
    interface List<E> extends Collection<E>
    	class ArrayList<E> implements List<E>
    	class LinkedList<E> implements List<E>
    	class Vector<E> implements List<E>
    interface Set<E> extends Collection<E>
    	class TreeSet<E> implements Set<E>
    	class HashSet<E> implements Set<E>

今天我们要说的是LinkedList集合,LinkedList集合是一种链表类型的数据结构,在创建LinkedList节点时,jvm会在内存中找到一个足够的空间存储,一个节点中包含三部分内容:前趋节点,本节点内的值,后继节点。前趋节点保存的是前面一个节点的地址,后继节点保存的是后面一个节点的地址 。通过前趋节点和后继节点组成了一个双向链表(双向链表指的是可以从首节点遍历到尾节点,也可以从尾节点遍历到首节点,我以前一直以为双向链表指的是链表的首尾是相连的)。

双向链表的特征是:增删快,查询慢,并且理论上存储数量无限制。在内存方面,链表不需要直接申请连续的空间,而是每当创建一个节点再申请一个节点的空间,相比于ArrayList对内存的压力小。

在这里插入图片描述

下面我们来分析一下LinkedList源码中的几个重要方法来理解LinkedList。

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有一个内部私有类Node,这个类就是Linkedlist双向链表的节点,每添加一个数据就会创建一个节点。这个节点由三部分组成:节点内元素,后继节点,前驱节点。

linkFirst(E e)方法l

在linkFirst方法中有first和last变量,是这样定义的

transient Node<E> first;
transient Node<E> last;

这是双向链表的成员变量,first为链表的头节点(始终指向链表的第一个节点),last为链表的尾节点(始终指向节点的最后一个节点)

当链表为空时,first和last都为null;

当链表不为空时,first的前驱节点为空,元素内容必定不为空,last后继节点必定为空。

下面是linkedList方法源码(注释为我自己注释的,源码中没有)

private void linkFirst(E e) {
    // 创建f保存头节点
    final Node<E> f = first;
    // 创建新的节点,并将后继节点指向头节点
    final Node<E> newNode = new Node<>(null, e, f);
    // 更新头节点
    first = newNode;
    // 判断链表之前是否为空
    if (f == null)
        // 若插入之前链表为空,尾节点也指向新节点
        last = newNode;
    else
        // 若链表不为空,不需要考虑尾节点,将插入之前头节点的前趋节点指向新的节点(更新之前头节点的前趋)
        f.prev = newNode;
    // 更新链表有效节点个数
    size++;
    // 更新链表修改次数保护线程安全
    modCount++;
}

linkFirst方法是创建一个新的节点插入到头节点之前,这个节点的后继节点为插入前的头节点。在插入完成后更新头节点指向(如果之前双向链表为空,同时更新尾节点指向)。

linkLast(E e)方法

void linkLast(E e) {
    // 创建保存尾节点的l节点
    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++;
}

linkLast方法是创建一个新的节点到尾节点之后,该节点的前趋节点为插入前的尾节点。在插入完成后更新尾节点指向(如果链表在插入前为空,同时更新头节点)。

linkBefore(E e, Node succ)方法

void linkBefore(E e, Node<E> succ) {
    // 创建pred节点保存指定节点的前趋节点
    // 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++;
}

linkBefore方法是在指定节点前面插入一个节点。

在这里插入图片描述

在链表中插入一个元素就是修改要插入位置前后两节点的指向。如图,要在指定节点前插入新的元素,就要修改指定节点的前趋节点的后继,和指定节点的前趋。

在源码中创建新节点时分别初始化了新节点的前趋和后继,即线1, 2。

然后通过 succ.prev = newNode; 修改了指定节点的前趋。即线3。

最后判断指定节点是否为头指针(判断指定节点前趋节点是否为空)来修改指定节点前趋节点的后继,即线4。

node(int index)方法

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

node方法是查询指定下标位置的元素。因为LinkedList查询是根据前趋后继查询,所以查询速度较慢。

这里采用折半查找将链表根据有效节点个数从中间分开,前半部分从前往后遍历,后半部分从后往前遍历。这样能够节省查询时间。

以上是我对LinkedList几个关键方法的分析,如有错误,希望能够指出,让我得以查漏补缺,谢谢。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NoMoneyError

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

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

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

打赏作者

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

抵扣说明:

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

余额充值