LinkedList源码分析

概述

LinkedList体系图

从体系图中我们可以看出来,LinkeList不仅有List的特点还有Queue队列的特点。

LinkedList实现了Serializable和Cloneable说明LinkedList可以序列化和克隆

image-20230531202407626

LinkedList特点:

LinkedList的底层数据接口是双向链表

LinkedList是线程不安全的

LinkedList成员变量

LinkedList共有三个成员变量

image-20230531202855991

size:LinkedList中的节点(Node)个数

first:LinkedList底层链表的头结点

last: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构造方法

LinkedList共有两个构造方法

  1. 空参构造方法

    public LinkedList() {
    }
    
  2. 带参构造方法

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    

带参的构造方法

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

可以传进来一个集合,然后按照集合中元素的顺序添加到LinkedList的尾部

addAll(c)方法

可以看到实际上是调用了addAll(size, c)这个方法

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

addAll(size, c)方法

public boolean addAll(int index, Collection<? extends E> c) {
    // 判断index是否越界
    checkPositionIndex(index);
	
    // 将要添加到链表的集合转化为Object[]数组
    Object[] a = c.toArray();
    // 数组长度
    int numNew = a.length;
    // 如果集合的个数是0,直接返回false
    if (numNew == 0)
        return false;
	
    // 定义两个节点,pred链表中index位置的节点的前驱节点,succ表示链表中index位置的节点
    Node<E> pred, succ;
    // 如果index == size,示当前链表还是空的,表示在链表尾部插入,否则,在链表内部插入
    if (index == size) {
       	// last的后继节点自然是空
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }
	// 循环将a中的元素添加到链表中
    for (Object o : a) {
        // 向下转型
        @SuppressWarnings("unchecked") E e = (E) o;
        // 封装节点
        Node<E> newNode = new Node<>(pred, e, null);
        // pred = null,表示链表还是空的
        if (pred == null)
            // 让头结点等于a中的第一个元素
            first = newNode;
        else
            // 如果不是空的话,将newNode作为pred的后继节点
            pred.next = newNode;
        // 更新pred的值,以便于在链表中添加后续a中的元素
        pred = newNode;
    }
    
	// 到这,a中的元素就以链表的中的节点串联起来了(也和index前边的节点串联起来了),但是还没有真正的上链,下边的操作就是真正的上链
    if (succ == null) {
        // succ == null,表示的就是之前的链表是空的,只要让last = pred即可
        last = pred;
    } else {
        // 链表不是空的,得让链表完整下来
        pred.next = succ;
        succ.prev = pred;
    }
	
    // 更新链表中节点的个数
    size += numNew;
    // 更新链表结构修改的个数
    modCount++;
    return true;
}

下图表示要向index=1的位置插入两个元素。

1

执行完循环之后的结构

1

现在可以看到肯定是不对的,所以要让链表完整,也就是执行pred.next = succ和succ.prev = pred

1

checkPositionIndex(index)方法

如果越界就会抛出异常

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

isPositionIndex(index)方法

判断是否越界了,没有越界返回true,否则返回false

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

LinkedList的添加方法

看看有什么关于添加的方法

image-20230531211946785

addAll的两个方法上边已经分析过了,下面看其他的方法

add(E):boolean

这个方法默认是向链表的尾部插入元素的,比较简单

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

linkLast(e)方法

void linkLast(E e) {
    // 保存之前的尾节点
    final Node<E> l = last;
    // 封装e,并将新节点的pre设置为l=last
    final Node<E> newNode = new Node<>(l, e, null);
    // 将last执行新节点
    last = newNode;
    // l = null,说明链表是空的
    if (l == null)
        // 将newNode赋值给first即可
        first = newNode;
    else
        // 让之前的last的next执行newNode
        l.next = newNode;
    size++;
    modCount++;
}

addFirst(E):void

在链表的头部插入元素

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

linkFirst(e)方法

private void linkFirst(E e) {
    // 用f保存之前的first
    final Node<E> f = first;
    // 封装新节点,并将新节点的next指向f也就是first
    final Node<E> newNode = new Node<>(null, e, f);
    // 更新first的值
    first = newNode;
    if (f == null) // 链表是空的话,直接让尾节点也指向新节点
        last = newNode;
    else
        // 不是空的话,让之前的头结点,也就是现在的第二个节点的prev指向新的节点
        f.prev = newNode;
    size++;
    modCount++;
}

addLast(E):void

默认是向链表的尾部添加元素

右下边的代码可以看到和add没区别,都是调用了linkLast(),不再分析

public void addLast(E e) {
    linkLast(e);
}

add(int, E):void

向链表中索引为index位置插入元素

public void add(int index, E element) {
    // 检查index是否越界,前边分析过,不再分析
    checkPositionIndex(index);
	
    // 如果index = size的话,就是向链表中的尾部插入元素,直接调用linkLast(element),上边分析过,不再分析
    if (index == size)
        linkLast(element);
    else
        // 向其他位置插入
        linkBefore(element, node(index));
}

linkBefore(element, node(index))方法

node(index)表示返回链表中索引为index的节点

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // 保存当前节点的前驱节点到pred中
    final Node<E> pred = succ.prev;
    // 封装新节点,并把新节点的前驱节点指向pred,后继节点指向succ
    final Node<E> newNode = new Node<>(pred, e, succ);
	// 将succ的前驱节点指向newNode
    succ.prev = newNode;
    if (pred == null) // 如果要在链表的索引0位置插入元素
        // 把newNode当成头节点
        first = newNode;
    else
        // 将newNode当成pred的后继节点
        pred.next = newNode;
    size++;
    modCount++;
}

想index=1的位置插入

初始状态

1

创建了newNode之后,我的图好抽象

1

执行了succ.prev = newNode;之后

1

执行了pred.next = newNode之后,就将newNode插入了链表里

1

node(index)方法

这个方法就体现出了双向链表比单向链表的好处,插入删除的时候效率要高

Node<E> node(int index) {
    // assert isElementIndex(index);因为前边checkPositionIndex(index)已经验证过了,所以这里不需要
   	// private boolean isElementIndex(int index) {
    //     return index >= 0 && index < size;
    // }
	
    // 将链表分为前半部分和后半部分,通过index和size/2的大小判断是从前半部分找还是后半部分找
    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的删除方法

看看有什么关于删除的方法

image-20230531221525028

remove():E

默认删除的是第一个节点

public E remove() {
    return removeFirst();
}

removeFirst()方法

public E removeFirst() {
    // 得到第链表的头节点
    final Node<E> f = first;
    // 如果头节点都是空了,还删啥删呀,直接报错
    if (f == null)
        throw new NoSuchElementException();
    // 不为空
    return unlinkFirst(f);
}

unlinkFirst(f)方法

private E unlinkFirst(Node<E> f) {
    // 拿到头节点保存的的值
    final E element = f.item;
    // 将当前头节点的后继节点保存下来
    final Node<E> next = f.next;
    // 删除头节点
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null) // 链表只有一个元素,删除完头节点,链表就是空的了,让last也等于空
        last = null;
    else
        // 不是空的话,让next成为头节点,将原先next.prev指向的头节点置空
        next.prev = null;
    // 链表数量减一
    size--;
    // 链表结构改变次数加1
    modCount++;
    // 返回删除的节点的item
    return element;
}

removeFirst():E

通过代码可以看到,和remove()方法一样,不再分析

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

removeLast():E

删除最后一个元素

public E removeLast() {
    // 尾节点赋值给l
    final Node<E> l = last;
    // 要删除的元素是空了,跑异常
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

unlinkLast(l)方法

private E unlinkLast(Node<E> l) {
   	// 拿到尾节点的item,用来作为返回值
    final E element = l.item;
    // 保存删除尾节点的前驱节点
    final Node<E> prev = l.prev;
    // 删除尾结点
    l.item = null;
    l.prev = null; // help GC
    // 让现在链表的尾结点指向原先链表的尾结点的前驱节点
    last = prev;
    if (prev == null) // 说明原先链表只有一个节点。删除完链表就空了
        first = null;
    else // 原先链表不只一个节点,将现在尾结点的next置空,之前是指着原先链表的尾结点的
        prev.next = null;
    // 链表元素减一
    size--;
    modCount++;
    // 返回item
    return element;
}

remove(int):E

删除指定位置的元素

public E remove(int index) {
    // 检查是否越界,前边分析过,不再分析
    checkElementIndex(index);
    return unlink(node(index));
}

unlink(node(index))方法

E unlink(Node<E> x) {
    // assert x != null;
    // 删除节点的item
    final E element = x.item;
    // 删除节点的前驱节点
    final Node<E> next = x.next;
    // 删除节点的后继节点
    final Node<E> prev = x.prev;
	
    
    if (prev == null) { // prev是空,说明删除的节点是头节点
        first = next;   // first指向next,现在next是头节点
    } else {
        // 将删除节点的前驱节点的后继节点指向删除节点的后继节点
        prev.next = next;
        // 再将删除节点的前驱节点置空
        x.prev = null;
    }
	
    if (next == null) { // prev是空,说明删除的节点是尾节点
        last = prev;    // last指向prev,现在prev是尾结点
    } else {
        // 将删除节点的后继节点的前驱节点指向删除节点的前驱节点
        next.prev = prev;
        // 将删除节点的的后继节点置空
        x.next = null;
    }
	
    // 将删除节点的item置空,这样这个node才算真正空,等待垃圾回收
    x.item = null;
    // 链表节点个数减一
    size--;
    modCount++;
    // 返回值
    return element;
}

删除是中间节点的情况

1

remove(Object):boolean

删除指定item的节点

public boolean remove(Object o) {
    if (o == null) { // 删除节点的item是空
        // 遍历链表
        for (Node<E> x = first; x != null; x = x.next) {
            // 找到的话,就调用上边分析过的unlink()方法,找到就会停止,不会删除全部
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else { // 删除节点的item不是空
        for (Node<E> x = first; x != null; x = x.next) {
            // 用equals判断,如果相等,就调用unlink()进行删除
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    // 没找到就返回false
    return false;
}

removeFirstOccurrance(Object):boolean

实际上调用的是remove(Object):boolean,分析过了,不再分析

public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

removeLastOccurrance(Object):boolean

和remove(Object):boolean基本一样,就是for循环从last开始

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

clear():void

public void clear() {
    // 遍历链表
    for (Node<E> x = first; x != null; ) {
        // 保存当前节点的下一个,如果不保存的话,后续的节点没有办法删除
        Node<E> next = x.next;
        // Node三个属性置空,等待GC
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    // 链表都空了,LinkedList的first和last也是空了
    first = last = null;
    // 节点的个数置0
    size = 0;
    modCount++;
}

LinkedList的根据索引查询节点的方法

先来看有些什么查询的方法

image-20230601122125242

get(int):E

获取指定索引位置的元素值,这中get的方式的查询效率其实是不高的,从node(index)的源码就可以知道,要经过遍历,虽然LinkedList采用了双向链表,但是还是要遍历一部分。不如ArrayList的get(int)方法,O(1)的时间复杂度。所以,如果经常要查找到的话,还是考虑用ArrayList

public E get(int index) {
    // 检查index是否越界
    checkElementIndex(index);
    // 不越界的话,就调用node(index)找到当前节点,然后再返回item的值
    return node(index).item;
}

getFirst():E

拿到链表第一个节点的值的item属性,判断first不为空,然后直接返回item的值

public E getFirst() {
    // 将first赋值给f
    final Node<E> f = first;
    // 判断是不是null
    if (f == null)
        throw new NoSuchElementException();
   	// 返回链表第一个元素的值
    return f.item;
}

getLast():E

返回链表最后一个节点的item属性

首先将last值赋值给l,l不空,直接返回l的item属性

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

LinkedList修改节点的方法

只有一个set()方法

image-20230601123908912

set(int, E):E

public E set(int index, E element) {
    // 检查要修改的索引是否越界
    checkElementIndex(index);
    // 拿到index在链表中代表的节点
    Node<E> x = node(index);
    // 拿到现在节点的item的值
    E oldVal = x.item;
    // 再将新的element赋值给index所代表的的节点
    x.item = element;
    // 再返回oldVal的值
    return oldVal;
}

LinkedList根据item返回节点index

image-20230601125209140

indexOf(Object):int

从链表的前边开始遍历,找到Object出现的第一个位置的索引

public int indexOf(Object o) {
    // 定义存储索引的变量index
    int index = 0;
    if (o == null) { // 还是o是否是空,如果是空的话,要用== 来比较,不是空的话,要用equals来比较
        // 循环链表,找到就返回
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        // o不是空,遍历链表,用equals判断
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    // 有找到就返回-1
    return -1;
}

lastIndexOf(Object):int

从链表的后边开始遍历,找到Object出现的第一个位置的索引

public int lastIndexOf(Object o) {
    // 因为是从后向前找嘛,index=size,如果index = size - 1的话,代码的结构和上边的代码基本就不会变了
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            // 先减一,这样index的值才代表当前正在遍历的节点
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    // 没有找到就返回-1
    return -1;
}

LinkedList作为双向队列

再回过头来看LinkedList的继承体系,可以看到LinkedList是实现了Deque的,二Deque继承了Queue,所以LinkedList既实现了Queue也实现了Deque

image-20230531202407626

Queue接口中定义的方法

image-20230601142000611

  1. 第一组:都是向队列的尾部添加元素。对于一个有限队列来说,队列满的话,再往里添加元素,add()会抛出异常,offer()只是返回false

    add(E):boolean

    offer(E):boolean

  2. 第二组:都是查看队列头部的元素,但是element当队列是空的话,会抛出异常。peek()会返回null

    element():E

    peek():E

  3. 第三组:都是弹出头部元素,但是remove()当队列是空的话,会抛出异常。poll()会返回null

    remove():E

    poll():E

Deque接口中定义的方法

image-20230601142831718

Deque双端队列

实现了这个接口的类,既可以作为队列使用,也可以作为栈使用(LinkedList)

Deque提供的方法,图片来源https://juejin.cn/post/6844903586095169549

image-20230601143156628

我们通过下边的表格来对照下双端队列是如何实现队列操作的,值得注意的是 Deque 实现了 Queue,所以 Queue 所有的方法 Deque 都有,下面比较的是Deque区别 Queue 的方法:

image-20230601143807794

LinkedList作为队列实现的Queue和Deque方法的不同

添加元素的方法

// Queue的方法
public boolean add(E e) {
    linkLast(e);
    return true;
}

// Deque中的方法
public void addLast(E e) {
    linkLast(e);
}

// Queue的实现
public boolean offer(E e) {
    return add(e);
}

// Deque的实现
public boolean offerLast(E e) {
        addLast(e);
        return true;
}

删除头元素的方法

所以,在删除头节点的方法中,Queue和Deque都是一样的

// Queue中的方法,会抛出异常
public E remove() {
    return removeFirst();
}

// 会抛出异常
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

// 如果头节点是空的话,会返回null。Queue
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

// 和poll一模一样
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

获取头节点元素的方法

所以,在获取头节点的方法中,Queue和Deque都是一样的

// Queue
public E element() {
    return getFirst();
}

// Deque,可以看出来element其实就是调用的getFirst(),头元素为空的话,抛出异常
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

// Queue,如果是空的直接返回空,不抛出异常
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

// 和peek()一模一样
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
 }

LinkedList作为栈

image-20230601145807276

push(),直接调用addFirst(e)

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

pop()直接调用removeFirst(e)

public E pop() {
    return removeFirst();
}

peek()直接调用peekFirst()

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

最主要的方法其实还是在作为List实现的

posted @ 2023-06-01 15:11  Sstarry  阅读(11)  评论(0)    收藏  举报