LinkedList源码解析

LinkedList源码解析

简介

LinkedList 是一个双向链表(内部是 Node 节点)实现的 List,并且还实现了 Deque 接口,它除了作为 List 使用,还可以作为队列或者来使用。

这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用 LinkedList,一方面是因为 Java 官方已经声明不建议使用 Stack 类,更遗憾的是,Java 里根本没有一个叫做 Queue 的类(它是个接口名字)。

关于栈或队列,现在的首选是 ArrayDeque,它有着比 LinkedList(当作栈或队列使用时)有着更好的性能。

image-20221129212840604

继承体系

image-20221129212705220

通过继承体系,我们可以看到 LinkedList 不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,所以它既能作为 List 使用,也能作为双端队列使用,当然也可以作为栈使用。

LinkedList 实现了 Cloneable 和 Serializable 接口,说明其可以被克隆,也可以被序列化。

同样的,LinkedList 被克隆的时候,和 ArrayList 一样,二者均是浅拷贝。

源码解析

属性

	// 元素个数
	transient int size = 0;
	
	// 双向链表的尾结点
    transient Node<E> first;
	
	// 双向链表的头结点
    transient Node<E> last;

属性很简单,定义了元素个数 size 和链表的首尾节点。

Node内部类

  • 典型的双链表结构。
    private static class Node<E> {
        E item; // data
        Node<E> next; // 前驱节点
        Node<E> prev; // 后继节点
        
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

构造方法

空参

   public LinkedList() {
   }

传入一个Collection集合

    public LinkedList(Collection<? extends E> c) {
        this();
        // 将c中的每一个元素构造成Node插入到链表中
        addAll(c);
    }
			     || 
			     \/ 
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
				   ||
				   \/
	/*
	 * 从指定的索引位置开始插入一个Collection
	 */
    public boolean addAll(int index, Collection<? extends E> c) {
       	// 检查index是否越界,index >= 0 && index <= size
        checkPositionIndex(index);
		// 将c转换为数组,并获取其长度
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        /*
         * pred 指定index位置的Node节点的前驱节点
         * succ 存储index位置的Node节点,插入完毕后拼接链表时要使用到这个节点
         */
        Node<E> pred, succ;
        // index == size 说明当前插入位置就是在链表的末尾开始
        if (index == size) {
            // succ置为null,因为直接在链表末尾插入时插入完毕不需要拼接
            succ = null;
            // pred直接指向尾结点即可
            pred = last;
        // index != size 说明此时就是在链表中插入
        } else {
            // succ存index位置的元素
            succ = node(index);
            // pred指向index的前驱节点
            pred = succ.prev;
        }
		
        // 遍历c中的每一个元素,依次进行插入。
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            // 构造节点,前驱节点就是pred
            Node<E> newNode = new Node<>(pred, e, null);
            // pred为null,说明链表为空,此时将first指向newNode
            if (pred == null)
                first = newNode;
            // 非空链表,将pred.next指向newNode
            else
                pred.next = newNode;
            // pred向后移动
            pred = newNode;
        }
                       
        // succ == null,即上面的if判断是 -> 当前是在链表末尾插入元素
        if (succ == null) {
            // 直接将last指向新的尾结点即可
            last = pred;
        // 非在链表末尾插入元素
        } else {
            /*
             * 此时pred指向的是尾结点
             * succ指向的是原index位置的节点
             * 此时需要做的事就是将两部分链表连接起来
             */
            pred.next = succ;
            succ.prev = pred;
        }
        // 当前元素个数 + numNew
        size += numNew;
        // 链表修改次数+1 
        modCount++;
        return true;
    }	

核心方法

添加元素

linkLast(E e)

	/*
	 * 此方法程序员无法调用,供程序员使用的add()/addLast()方法内部调用的就是此方法。
	 * 作用:在链表末尾添加节点。
	 */
	void linkLast(E e) {
        // l指向尾结点
        final Node<E> l = last;
        // 构造newNode,newNode的前驱指向尾结点。
        final Node<E> newNode = new Node<>(l, e, null);
        // 尾结点指向newNode
        last = newNode;
        // l == null 说明当前链表中没有元素
        if (l == null)
            // 将first也指向newNode
            first = newNode;
        // 原尾结点的next指向newNode成功添加
        else
            l.next = newNode; 
        size++;
        // modCount+1 表示修改了一次链表结构
        modCount++;
    }

linkFirst(E e)

    /*
	 * 此方法程序员无法调用,供程序员使用的addFirst()方法内部调用的就是此方法。
	 * 作用:将新添加的节点作为链表头部。
	 */
	private void linkFirst(E e) {
        // f指向原头结点
        final Node<E> f = first;
        // 创建新节点,前驱设置为null
        final Node<E> newNode = new Node<>(null, e, f);
        // first指向newNode,即newNode是当前的头结点
        first = newNode;
        // f == null,说明原链表中没有节点
        if (f == null)
            // 此时向链表中添加一个元素后,链表中有一个元素,所以将last也指向newNode。
            last = newNode;
        else
            // 将原头结点的前驱指向newNode。
            f.prev = newNode;
        size++;
        // modCount+1 表示修改了一次链表结构
        modCount++;
    }

add() & offer()

   	/*
   	 * 在链表尾部添加元素
   	 */
	public boolean add(E e) {
        // 调用的是linkLast()
        linkLast(e);
        return true;
    }

// ------------------------------------------------------------
	/*
	 * 在指定索引位置前插入Node。
	 */
    public void add(int index, E value) {
        // 检查索引是否越界
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);
        else
            // 将value构造成Node插入到index位置Node的前面。
            linkBefore(element, node(index));
    }
			    ||
 			    \/
	/*
	 * @param succ,要在succ位置前面插入一个Node。
	 */
    void linkBefore(E e, Node<E> succ) {
        // 获取succ的前驱
        final Node<E> pred = succ.prev;
        // 构造一个Node,前驱是pred,后继是succ
        final Node<E> newNode = new Node<>(pred, e, succ);
		// succ的前驱指向newNode
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        // 一般情况 pred的后继指向newNode 完成插入操作。
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

// ------------------------------------------------------------
	/*
   	 * 在链表尾部添加元素
   	 */
	public boolean offer(E e) {
    	// 调用add()
        return add(e);
    }

addFirst() & push() & offerFirst()

    /*
     * 头部添加元素
     */
	public void addFirst(E e) {
        // 调用的是linkFirst()
        linkFirst(e);
    }

// --------------------------------------------
   public void push(E e) {
        // 调用addFirst()
        addFirst(e);
   }

// --------------------------------------------
    public boolean offerFirst(E e) {
        // 调用addFirst()
        addFirst(e);
        return true;
    }

addLast() & offerLast()

	/*
	 * 尾部添加元素
	 */
	public void addLast(E e) {
        // 调用的是linkLast()
        linkLast(e);
    }

// --------------------------------------------
	public boolean offerLast(E e) {
        // 调用addLast()
        addLast(e);
        return true;
    }

在中间添加元素的方法也很简单,典型的双链表在中间添加元素的方法。

添加元素的三种方式大致如下图所示:

image-20221201213454154

在队列首尾添加元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)

在中间添加元素比较低效,首先要先找到插入位置的节点,再修改前后节点的指针,时间复杂度为 O ( n ) O(n) O(n)

查看元素

peek()

    /*
     * 查看头结点
     */
	public E peek() {
        // 获取头结点
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

peekFirst()

	/*
	 * 查看头结点
	 */     
	public E peekFirst() {
        // 获取头结点
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

peekLast()

    /*
	 * 查看尾结点
	 */  
	public E peekLast() {
        // 获取尾结点
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

删除元素

unlinkFirst(Node<E> f)

	/*
	 * 删除元素的核心方法,外部无法调用。
	 * 能进入这个方法的前提就是 (f为头结点 && f != null)
	 * 此方法作用:将头结点删除。
	 */
	private E unlinkFirst(Node<E> f) {
        // 获取头结点的value,最后返回。
        final E element = f.item;
        // next是f的后继节点
        final Node<E> next = f.next;
        // 将头结点f的value置为null,便于GC。
        f.item = null;
        // 将头结点的后继置为null
        f.next = null; // help GC
        // 头结点指向后继
        first = next;
       	/*
       	 * next后继为null,说明原链表中只有一个节点,此时删除之后链表中没有节点了,所以将last指向null
       	 */	
        if (next == null)
            last = null;
        else
            // 普通情况,直接将next的前驱置为null,即彻底断开原头结点。
            next.prev = null;
        size--;
        modCount++;
        // 最终将原头结点的value返回。
        return element;
    }

unlinkLast(Node<E> l)

	/*
	 * 删除元素的核心方法,外部无法调用。
	 * 能进入这个方法的前提就是 (l为尾结点 && l != null)
	 * 此方法作用:将尾结点删除。 
	 */
	private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        // 获取尾结点的前驱节点
        final Node<E> prev = l.prev;
        // 将尾结点的value和前驱节点置为null
        l.item = null;
        l.prev = null; // help GC
        // 将last指向尾结点的前驱节点
        last = prev; 
        // prev == null,说明删除尾结点后链表为null,此时需要将first也置为null。
        if (prev == null)
            first = null;
        else
            // 普通情况,将前驱节点的后继置为null,彻底断开尾结点
            prev.next = null;
        size--;
        modCount++;
        // 返回原尾结点的value。
        return element;
    }

removeFirst() & pollFirst() & pop() & poll()

    /*
     * 删除头结点
     */
	public E removeFirst() {
        final Node<E> f = first;
        // 没有元素,抛出异常
        if (f == null)
            throw new NoSuchElementException();
        // 调用unlinkFirst() 删除头结点
        return unlinkFirst(f);
    }

// ------------------------------------------------
    public E pop() {
    	// 调用removeFirst()删除头结点
        return removeFirst();
    }

// ------------------------------------------------
    public E pollFirst() {
    	final Node<E> f = first;
        // 调用unlinkFirst() 删除头结点
        return (f == null) ? null : unlinkFirst(f);
    }

// ------------------------------------------------
    public E poll() {
        final Node<E> f = first;
        // 调用unlinkFirst() 删除头结点
        return (f == null) ? null : unlinkFirst(f);
    }

removeLast() & pollLast()

    /*
     * 删除尾结点 
     */
	public E removeLast() {
        final Node<E> l = last;
        // 没有元素,抛出异常
        if (l == null)
            throw new NoSuchElementException();
        // 调用unlinkLast() 删除尾结点
        return unlinkLast(l);
    }

// --------------------------------------------
    public E pollLast() {
        final Node<E> l = last;
        // 调用unlinkLast()删除尾结点
        return (l == null) ? null : unlinkLast(l);
    }

remove(int index)

	/*
	 * 删除指定索引位置的Node。
	 */    
	public E remove(int index) {
        // 检查索引
        checkElementIndex(index);
        // 遍历找到index位置的Node,然后将其删除。
        return unlink(node(index));
    }
			    ||
 			    \/
// --------------------------------------------
	E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        // 获取x节点的后继
        final Node<E> next = x.next;
        // 获取x节点的前驱
        final Node<E> prev = x.prev;
		// 如果前驱节点为空 则说明当前元素是链表的头结点 直接将first指向当前节点的后继节点
        if (prev == null) {
            first = next;
        } else { // 前驱节点不为空
            // x.prev.next = x.next 变相的删除x节点
            prev.next = next;
            // 断开x的前驱节点 彻底删除x
            x.prev = null;
        }
		// 如果后继节点为空 则说明当前元素是链表的尾结点 直接将last指向当前节点的前驱节点
        // 内部逻辑与上面一样
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
		// 将当前元素置为空 help GC
        x.item = null;
        size--;
        modCount++;
        return element;
    }

删除元素的三种方法都是典型的双链表删除元素的方法,大致流程如下图所示。

image-20221201213617929

在队列首尾删除元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)

在中间删除元素比较低效,首先要找到删除位置的节点,再修改前后指针,时间复杂度为 O ( n ) O(n) O(n)

获取元素

getFirst()

   	 /*
   	  * 获取头结点的value值
   	  */
	 public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

getLast()

    /*
     * 获取尾节点的value值
     */
	public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

get()

    /*
     * 获取指定索引位置节点的value。
     */
	public E get(int index) {
        // 检查索引是否越界(index >= 0 && index < size)
        checkElementIndex(index); 
        return node(index).item;
    }

// -----------------------------------------------------------
	/*
	 * 遍历链表返回指定位置的Node。
	 */
	Node<E> node(int index) {
        // 这里判断要查找的Node是在链表的前半部分,从头节点向后找
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        // 要查找的Node在链表的后半部分,从尾结点向前找
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

常用方法

set()

    /*
     * 修改指定索引位置节点的value。
     */
	public E set(int index, E element) {
        // 检查索引是否越界
        checkElementIndex(index);
        // 获取index位置的Node
        Node<E> x = node(index);
        // 获取value
        E oldVal = x.item;
        // 修改value
        x.item = element;
        // 返回原value
        return oldVal;
    }

indexOf()

    /*
     * 从链表的头结点开始找,找到指定value的Node第一次出现的索引。
     */	
	public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            // 从头开始遍历链表
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        // 不存在,返回-1
        return -1;
    }

contains()

	/*
	 * 判断链表中是否存在指定的value的Node
	 * 底层调用的是indexOf() 
	 */  		
	public boolean contains(Object o) {
        // indexOf()返回-1说明链表中没有指定value的Node
        return indexOf(o) >= 0;
    }

size()

    /*
     * 返回链表中的元素个数
     */
	public int size() {
        return size; // 直接返回size
    }

栈操作

前面我们说了,LinkedList 是双端队列,还记得双端队列可以作为栈使用吗?

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

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

栈的特性是 LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。

总结

(1)LinkedList 是一个以 双向链表 实现的 List;

(2)LinkedList 还是一个双端队列,具有 队列双端队列 的特性;

(3)LinkedList 在队列首尾添加、删除元素非常高效,时间复杂度为 O ( 1 ) O(1) O(1)

(4)LinkedList 在中间添加、删除元素比较低效,时间复杂度为 O ( n ) O(n) O(n)

(5)LinkedList 不支持随机访问,所以访问非队列首尾的元素比较低效;

(6)LinkedList 在功能上等于 ArrayList + ArrayDeque




参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小成同学_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值