JDK源码解析之LinkedList

本文详细介绍了Java中的LinkedList数据结构,它是一个基于双向链表实现的线程不安全的有序序列。LinkedList支持高效地在任意位置插入和移除元素,同时也实现了List、Deque和Serializable接口。文章通过类图、属性、构造方法和关键操作(如添加、删除、查找元素)的解析,深入探讨了LinkedList的工作原理和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的,是线程不安全的,允许元素为null的双向链表

  • LinkedList本质就是双向链表,每个节点都有指向前驱和指向后继的指针,从而构成链表。
  • 相对于之前讲的ArrayListLinkedList 使用较少,它的源码也相对简单许多。

二、类图

可以看到LinkedList实现了四个接口

  • java.util.List:提供数组的添加、删除、修改、迭代遍历等操作
  • java.util.Deque :提供双端队列的功能,LinkedList 支持快速的在头尾添加元素和读取元素,所以很容易实现该特性。
  • java.io.Serializable :表示 LinkedList 支持序列化的功能
  • java.lang.Cloneable :表示 LinkedList 支持克隆

LinkedList同时继承了一个抽象类

  • java.util.AbstractSequentialList:它是AbstractList的子类,不过同ArrayList一样,LinkedList大量重写了其基继承的抽象类的方法。

三、属性和构造方法

1. 属性

一共有三个属性

/**
 * 链表大小
 */
transient int size = 0;

/**
 * 头节点
 */
transient Node<E> first;

/**
 * 尾节点
 */
transient Node<E> last;

其中Node类就是链表中的一个节点,它是LinkedList的内部类

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

2. 构造方法

一共有两个构造方法

  • 1)无参构造方法

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }
    
  • 2)有参构造方法

    /**
         * Constructs a list containing the elements of the specified
         * collection, in the order they are returned by the collection's
         * iterator.
         *
         * @param  c the collection whose elements are to be placed into this list
         * @throws NullPointerException if the specified collection is null
         */
    public LinkedList(Collection<? extends E> c) {
        this();
        //添加所有集合元素到链表中,addAll方法后面讲解
        addAll(c);
    }
    

四、添加元素

添加元素,分成三个情况

  • 1)添加元素到队头
  • 2)添加元素到队尾
  • 3)添加元素到中间

1. 添加单个元素

  • add(E)

    插入单个元素,默认为尾插法。

    public boolean add(E e) {
        //尾插法添加元素
        linkLast(e);
        return true;
    }
    

    内部调用linkLast方法,新增的元素在链表末尾

    /**
         * 尾插法
         */
    void linkLast(E e) {
        //记录原尾节点
        final Node<E> l = last;
        //创建新节点,它的前驱节点为原尾节点,后继节点默认为null
        final Node<E> newNode = new Node<>(l, e, null);
        //新插入的节点作为新的尾节点
        last = newNode;
        //如果原尾节点为null,说明链表是空的,因此头节点为新插入的节点
        if (l == null)
            first = newNode;
        //否则链表不为空,将原尾结点的后继指向新插入的节点
        else
            l.next = newNode;
        //更新链表大小
        size++;
        //更新链表修改次数
        modCount++;
    }
    

    默认为尾插法,代码逻辑就是简答的双向链表尾插法逻辑,较为简单。

  • add(int, E)

    指定位置插入元素

    public void add(int index, E element) {
        //检查位置是否合法,即index必须满足0<=index<=size,否则抛出数组越界异常
        checkPositionIndex(index);
    	//如果插入位置就是末尾或者链表一开始就是空的,直接调用尾插法函数
        if (index == size)
            linkLast(element);
        //否则调用linkBefore函数
        else
            //这里的node(index)就是找到index位置的元素
            linkBefore(element, node(index));
    }
    
    //这里就是遍历取出index位置的节点
    Node<E> node(int index) {
        // assert isElementIndex(index);
    	//这里判断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;
        }
    }
    

    接下来分析linkBefore函数,入参为要插入的元素index位置处的节点

    /**
         * Inserts element e before non-null Node succ.
         */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //保存原index位置的前驱节点
        final Node<E> pred = succ.prev;
        //创建新节点,前驱节点为index位置的前驱节点,后继节点为index位置的节点
        final Node<E> newNode = new Node<>(pred, e, succ);
        //原index位置的节点的前驱指向新节点
        succ.prev = newNode;
        //如果原index位置的节点前驱节点为空,说明index=0,即要插入位置为第一个位置,所以插入后新节点为头结点
        if (pred == null)
            first = newNode;
        //否则原index位置的后继节点指向新节点
        else
            pred.next = newNode;
        //更新链表大小
        size++;
        //更新链表修改次数
        modCount++;
    }
    

    指定位置的插入操作大家可以画图分析,仅看代码可能比较抽象。

  • addFirst(E)

    添加元素到链表的头部,即头插法

    /**
         * Inserts the specified element at the beginning of this list.
         *
         * @param e the element to add
         */
    public void addFirst(E e) {
        linkFirst(e);
    }
    

    内部调用了linkFirst方法,新增的元素在链表头部

    private void linkFirst(E e) {
        //保存原头结点
        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++;
    }
    
  • addLast(E)

    添加元素到链表的尾部,即尾插法

    public void addLast(E e) {
        //尾插法
        linkLast(e);
    }
    

    调用方法linkLast方法,尾插法,上面已经分析过了,这么不再赘述。

  • push(E)/offer(E)

    因为LinkedList实现了Queue接口,所以它实现了**push(E)/offer(E)**方法,添加元素到链表的头尾

    //头插法
    public void push(E e) {
        addFirst(e);
    }
    //尾插法
    public boolean offer(E e) {
        return add(e);
    }
    

    代码逻辑上面已经分析过了,这么不再说明

2. 添加多个元素

  • addAll(Collection<? extends E>)

    在尾部批量添加元素

    public boolean addAll(Collection<? extends E> c) {
        //在尾部批量插入元素
        return addAll(size, c);
    }
    

    可以看到它调用方法addAll(size, c),正好是另一个添加多个元素的方法,我们下面分析。

  • addAll(int, Collection<? extends E>)

    指定位置添加元素

    public boolean addAll(int index, Collection<? extends E> c) {
        //检查index是否合法
        checkPositionIndex(index);
    	//将入参集合c转换成数组
        Object[] a = c.toArray();
        //待插入的数组长度
        int numNew = a.length;
        //如果数组为0,直接返回false代表当前链表未更改
        if (numNew == 0)
            return false;
    	//这里是获得第index位置的节点succ,和其前一个节点 pred
        Node<E> pred, succ;
        //如果index位置就是链表大小,表示插入队尾,所以pred为原尾节点,succ为null
        if (index == size) {
            succ = null;
            pred = last;
        } 
        //否则,pred为原index位置的节点的前驱,succ为原index位置的节点
        else {
            succ = node(index);
            pred = succ.prev;
        }
    	//遍历数组a,添加到pred的后面
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            //如果pred为null ,说明当前位置为链表第一个位置,first为 null,则直接将first指向新节点
            if (pred == null)
                first = newNode;
            //否则将pred的后继指向新节点
            else
                pred.next = newNode;
            //修改pred指向,进行插入数据
            pred = newNode;
        }
    	//上述元素插入结束后,需要修改succ和pred 的指向
        //如果succ为空,根据上面的分析,说明插入位置就是队尾,所以pred就是尾节点
        if (succ == null) {
            last = pred;
        } 
        //否则,说明所有元素插入到原index位置的节点(succ)的前面,因此需要将pred的后继指向succ,succ的前驱指向pred
        else {
            pred.next = succ;
            succ.prev = pred;
        }
    	//更新链表大小
        size += numNew;
        //更新数组修改次数
        modCount++;
        //返回true,表示链接有更新
        return true;
    }
    

    如果不好理解可以自己画图分析

五、删除元素

  • remove(int)

    删除指定位置的元素,并返回原位置的元素

    public E remove(int index) {
        //校验index是否合法
        checkElementIndex(index);
        return unlink(node(index));
    }
    

    再看unlink方法

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        // 保存x的元素,前驱和后继
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        
        //更新前驱指向
    	//如果前驱为空,说明x是原头结点,删除后需要更新头结点指向
        if (prev == null) {
            first = next;
        } 
       	//否则就将x前驱节点的后继指向x的后继,x的前驱置空
        else {
            prev.next = next;
            x.prev = null;
        }
    
        //更新后继指向
        //如果x的后继为空,说明x是原尾节点,删除后需要更新尾结点指向
        if (next == null) {
            last = prev;
        } 
        //否则就将x的后继节点的前驱指向x的前驱,x的后继置空
        else {
            next.prev = prev;
            x.next = null;
        }
    	
        //删除x的元素值
        x.item = null;
        //更新链表大小
        size--;
        //更新链表更新次数
        modCount++;
        //返回原位置的元素值
        return element;
    }
    

    该方法的逻辑就是双向链表的删除操作

  • remove(Object)

    移除首个对象,从头到尾的顺序执行

    public boolean remove(Object o) {
        //如果对象为空
        if (o == 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;
    }
    

    代码较为简单,就是从头结点遍历到尾结点,删除第一个遇到的节点,并返回true,如果没有找到返回false

  • removeFirst()/removeLast()

    删除队首或队尾元素,并返回删除的元素。如果不存在,则抛出NoSuchElementException异常

    public E removeFirst() {
        //获取头结点
        final Node<E> f = first;
        //如果头结点为null,抛异常
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    public E removeLast() {
        //获取尾结点
        final Node<E> l = last;
        //如果尾结点为null,抛异常
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    

    调用方法分别为unlinkFirstunlinkLast,分别是删除头结点和尾节点的操作。

    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //保存头结点元素值
        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 = null;
        //否则正常更新新头结点的前驱为null
        else
            next.prev = null;
        //更新链表长度
        size--;
        //更新链表修改次数
        modCount++;
        //返回原头结点值
        return element;
    }
    
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        //保存尾结点元素值
        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;
        //否则正常更新新尾结点的后继为null
        else
            prev.next = null;
        //更新链表长度
        size--;
        //更新链表修改次数
        modCount++;
        //返回原尾结点值
        return element;
    }
    
  • removeFirstOccurrence(Object)/removeLastOccurrence(Object)

    删除链表首个或最后匹配的节点

    public boolean removeFirstOccurrence(Object o) {
        //调用remove(Object)方法,这里不再赘述
        return remove(o);
    }
    
    //原理同remove(Object)方法,只不过倒序遍历链表
    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;
    }
    
  • remove()

    删除头结点

    public E remove() {
        //直接调用方法removeFirst(),这里不再赘述
        return removeFirst();
    }
    

六、查找元素

  • indexOf(Object)

    获取首个指定元素的位置,如果不存则返回-1

    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++;
            }
        }
        return -1;
    }
    

    逻辑较为简单,即从队头遍历到队尾查找

  • lastIndexOf(Object)

    获取最后一个指定元素的位置,如果不存则返回-1

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                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;
            }
        }
        return -1;
    }
    

    逻辑同indexOf(Object)方法,只不过是从队尾遍历到队头

  • contains(Object)

    查询是否包含指定元素

    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
    

    本质就是调用indexOf方法查找

  • get(int)

    查找指定位置的元素

    public E get(int index) {
        //校验index是否合法
        checkElementIndex(index);
        //调用node(index)方法查找指定位置的节点
        return node(index).item;
    }
    
  • peekFirst()/peekLast()

    因为LinkedList实现了Deque接口,所以它实现了peekFirst()和peekLast()方法,分别获得元素到链表的头和尾

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

    注意:这里如果头和尾节点不存在不好抛出异常,只是返回null

  • peek()

    同peekFirst()方法

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

    获取头结点元素,不存在则抛出NoSuchElementException异常

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

七、其余操作

  • set(int, E)

    修改指定位置的元素,并返回原值

    public E set(int index, E element) {
        //检查index是否合法
        checkElementIndex(index);
        //找到指定位置的节点
        Node<E> x = node(index);
        //保存原值
        E oldVal = x.item;
        //更新值
        x.item = element;
        //返回原值
        return oldVal;
    }
    
  • clear()

    清空链表

    public void clear() {
        //循环清除链表中每个节点
        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;
        //重置链表长度为0
        size = 0;
        //更新链表修改次数
        modCount++;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值