Java中LinkedList详解(附带源码分析)

231 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第十二天,点击查看活动详情

Java中LinkedList详解(附带源码分析)

基于JDK 1.8源码分析

前言

我们在上一篇中了解到了ArrayList,作为ArrayList的同门师兄弟的LinkedList,他们有什么区别呢?我们先看一下LinkedList的结构图

4761309-cd3d6ea4df99a344.png

我们通过这个结构图,我们简单介绍一下,每个接口和类的作用

  • Collection接口Collection接口是所有集合的根节点,它定义了一组允许重复的对象
  • List接口List是继承与Collection接口,List接口的集合类中的元素是对象有序且可重复
  • AbstractCollection抽象类:主要是实现了Collection接口的contains()toArray()removeAll()toString()
  • AbstractList抽象类:主要实现了List接口的方法
  • Cloneable接口:它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中
  • Serializable接口:表示可以序列化和反序列化
  • RandomAccess接口:该接口表示可以支持快速随机访问
  • Deque 接口: 用来实现一个双端队列
  • AbstractSequentialList类:LinkedList的父类,是List接口的简化版实现

LinkedList实现原理

LinkedList底层内部实现不是数组,而是双向链表

基本属性

//长度
transient int size = 0;

//指向上一个节点
transient Node<E> first;

//指向下一个节点
transient Node<E> last;

transient关键字是防止属性被序列化,NodeLinkedList的内部类,我们来看一下源码:

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-01.png

对于fitst来说没有上一个节点所有prevnull,对于last来说没有下一个节点,所以prevnull

初始化

LinkedList的构造函数比ArrayList少了一个,只有两个构造方法:

//空构造函数,什么也不做
public LinkedList() {
}

//将一个元素集合添加到LinkedList中
public LinkedList(Collection<? extends E> c) {
}

添加元素

add(E e)

通常我们使用add(E e)来添加元素,那我们看看源代码

public boolean add(E e) {
    linkLast(e);
    return true;
}

我们发现在add()中就只有两行代码,我们来看一下linkLast(e)的代码

void linkLast(E e) {
    //这里的last就是链表的最后一个Node,如果是初次添加的话为null
    final Node<E> l = last;
    
    //创建一个新的Node,对于新Node来说之前的last就是上一个Node,last为null
    final Node<E> newNode = new Node<>(l, e, null);
    
    //上一个lastNode指向新Node
    last = newNode;
    
    //判断是否为初次添加,初次添加会把链头first赋值
    if (l == null)
        first = newNode;
    else
        //不是初次添加上一个Node的next指向newNode
        l.next = newNode;
    size++;
    modCount++;
}

如果我们第一次添加的话,first和last都会指向newNode的结果,这时候还没有形成一个完整的双向链表,是一个断链的状态,结构就会如下图:

1665571344952.jpg

如果我们添加第二个元素后呢,它的结构就会如下图:

1665572203685.jpg

addFirst() 和 addLast()

我们除了可以调用add(E e)来实现添加,在LinkedList中天提供了addFirst()addLast()方法

  • addFirst():将元素添加到第一位
  • addLast():将元素添加到末尾

我们我一下addFirst()的内部实现:

public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    //获取到first
    final Node<E> f = first;
    
    final Node<E> newNode = new Node<>(null, e, f);
    
    //给first赋值
    first = newNode;
    
    //判断是否第一次添加元素
    if (f == null)
        //第一次添加last也赋值给last
        last = newNode;
    else
        //把之前的first的Node的prev指向新Node
        f.prev = newNode;
    size++;
    modCount++;
}

addLast()addFirst()也是类似,我在这里就不做过多的解释啦

删除

上面我们讲解了一下add()的原理,我们接下来讲解一下remove()的方法的源代码:

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

public boolean remove(Object o) {
    //判断删除的元素是否为null
    if (o == null) {
        //若是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;
}

我们发现在删除的最后,是使用unlink()来进行删除的

E unlink(Node<E> x) {

    //这个Node的所有属性拿出来
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    //下面的操作就是更新当前节点prev和next,然后把当前节点上的元素设置为null
    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;
}

LinkedListremove()的方法有很多,原理基本一致,这里不做过多的讲解

  • remove():删除第一个节点

  • remove(int):删除指定位置的节点

  • remove(Object):删除指定元素的节点

  • removeFirst():删除第一个节点

  • removeLast():删除最后一个节点