java容器之LinkedList源码解析(jdk8)

一、LinkedList简介

LinkedList的数据结构为双向链表结构,链表数据结构的特点是每个元素分配的空间不必连续,所以它的特点是插入和删除数据的效率非常高,但随即访问元素的效率很低,因为链表没有下标,所以只能通过遍历来访问元素。结构如下图所示。
在这里插入图片描述

二、LinkedList类的注解

在读源码之前,我们来看看LinkedList类上都注释了一些什么内容。
1)LinkedList是List和Deque接口的双向链表实现。实现所有可选列表操作,并允许所有元素(包含null)。
2)对于双向链表,所有操作都可以预期。索引到列表中的操作将从开头或结尾遍历列表,以较接近制定索引为准。
3)请注意,此实现不同步。如果多个线程同时访问链表,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。可以使用List list = Collections.synchronizedList(new LinkedList(…));方法来防止意外对列表的非同步访问。
4)这个类的iterator和listIterator方法返回的迭代器是快速故障的:如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法,迭代器将抛出ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。注意,并不能保证迭代器的快速故障行为,因为通常来说,在存在非同步并发修改的情况下,不可能做出任何严格的保证。故障快速迭代器以最大的努力抛出ConcurrentModificationException。因此,编写一个依赖于此异常来判断其正确性的程序是错误的:迭代器的快速故障行为应该只用于检测bug。
总结:从以上注释中可以看出,LinkedList是双向链表结构、可以包含null元素、只能通过遍历来访问元素、LinkedList是非线程安全的(可以通过List list = Collections.synchronizedList(new LinkedList(…));方法来防止意外对列表的非同步访问)。
三、源码分析
1)类的继承实现关系
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList继承了AbstractSequentialList类并实现了List、Deque(双端队列)接口。
2)内部类

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

内部类Node就是实际的元素节点,用来存放实际元素的地方。
3)构造方法

//无参构造方法
public LinkedList() { }
//有参构造方法(将collections集合中的元素全部添加进LinkedList双向链表中)
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

4)成员变量

transient int size = 0;//链表元素数量
transient Node<E> first;//链表的第一个元素
transient Node<E> last;//链表的最后一个元素

5)链接e作为第一个元素

private void linkFirst(E e){
    final Node<E> f = first;//将第一个元素赋值给f
    final Node<E> newNode = new Node<>(null, e, f);//newNode为第一个节点,那么prev为null,item为e元素,next为原来的fist节点
    first = newNode;//将新节点设置为first(第一个元素)
    if (f == null)//如果first为null,那么表示链表为空,则newNode既是第first元素又是last元素。
        last = newNode;
    else //如果first不为null,那么表示链表不为空且含有其他元素,则将原来的first元素的prev元素设置为newNode(第一个元素)
        f.prev = newNode;
    size++;//链表的大小+1
    modCount++;链表的修改次数+1
}

在这里插入图片描述
6)链接e作为最后个元素

void linkLast(E e) {
    //将尾部节点赋值给I
    final Node<E> l = last;
    //创建新的节点,由于是最后一个节点,所以next为null
    final Node<E> newNode = new Node<>(l, e, null);
    //将新节点newNode赋值为last
    last = newNode;
    if (l == null)//如果I为null,那么就表示该链表为空,需要将新节点赋值给first和last
        first = newNode;
    else //如果链表不为空,将原来last节点的next设置为新节点,由新节点来代替last节点
        l.next = newNode;
    size++;//链表的大小+1
    modCount++;链表的修改次数+1
}

在这里插入图片描述
7)在非null节点之前插入一个节点

void linkBefore(E e, Node<E> succ) {//将节点e插入在succ节点之前
    // assert succ != null;
    //将succ节点的perv赋值给pred
    final Node<E> pred = succ.prev;
    //创建新节点,将perv设置成perd,next设置为succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    //将succ节点的perv设置为新节点
    succ.prev = newNode;
    if (pred == null)//如果succ节点的perv为null,那么说明succ为第一个节点,newNode插入在succ之前,那么需要设置newNode为first节点
        first = newNode;
    else //如果succ不为first节点,那么需要要将perd(原succ节点的perv)的next设置为newNode
        pred.next = newNode;
    size++;//链表的大小+1
    modCount++;链表的修改次数+1
}

在这里插入图片描述
8)删除链接非空的第一个节点f。

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    //将f节点赋值给element
    final E element = f.item;
    //将f节点的next节点赋值给next
    final Node<E> next = f.next;
    //将f节点设置为null
    f.item = null;
    //将f节点next元素设置为null
    f.next = null; // help GC方便GC,垃圾回收
    //将原first节点的next元素设置为first节点
    first = next;
    if (next == null)//如果next为null,则说明该连表只有一个元素,去除之后,链表为空链表
        last = null;
    else  //如果链表有多个元素,则需要将next的perv设置为null,由next代替原来的first节点
        next.prev = null;
    size--;//链表数量-1
    modCount++;//链表操作次数+1
    return element;//将节点数据返回
}

在这里插入图片描述
9)删除链表中的某一个节点

E unlink(Node<E> x) {
    // assert x != null;
    //将x元素的item、perv、next元素都赋值出来
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
     //判断x元素是否为first节点
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;//将x节点的perv设置为null
    }
    //判断x元素是否为last节点
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;//将x节点的next设置为null
    }
    //将x节点本身设置为null
    x.item = null;
    size--;//链表大小-1
    modCount++;//链表操作次数+1
    return element;//返回x节点元素
}

在这里插入图片描述
10)在链表中插入集合

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);//验证index是否合法,index必须>=0以及<=size

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;//pred:perv,succ:item
    if (index == size) {//如果index为size,那么就是在链表的末尾插入集合
        succ = null;
        pred = last;
    } else {//在链表的中间index处插入集合
        succ = node(index);
        pred = succ.prev;
    }
    //将需要插入集合中的每一个元素都链接起来,构造成双向链表
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)//如果perd为null,则说明,原链表为空链表
            first = newNode;
        else//原链表不为空
            pred.next = newNode;
        pred = newNode;
    }
    //判断是否在链表末尾的地方进行插入集合
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;//链表的大小+集合的大小
    modCount++;//修改次数+1
    return true;
}

在这里插入图片描述
11)清空链表

public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
   //通过遍历的方式,将所有的节点都设置为null
    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++;
}

12)获取链表的第index个元素

Node<E> node(int index) {
    // assert isElementIndex(index);
    //size >> 1是位移运算,相当于size/2,由于LinkedList是双向链表结构,没有下标,获取其中的元素时只能通过遍历的方式来获取,可以从头部开始遍历,也可以从尾部开始遍历,这里判断index是否小于size/2的目的是为了得出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;
    }
}

以上只给出了LinkedList常用方法源码的解读。
这是小生第一次写博客,各位大佬多多关照,有错误之处请指出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值