【Java集合源码剖析】LinkedList源码剖析

本文详细解析了LinkedList的源码实现,展示了其双向链表结构,并总结了关键特性,包括非线程安全性、序列化支持、高效插入删除操作等。

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

推荐一篇分析LinkedList的博客,非常不错:传送门

LinkedList是基于双向链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。

LinkedList同样是非线程安全的,只在单线程下适合使用。

LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。

源码如下。

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{

    // 链表尺寸
    transient int size = 0;

    /**
     * 头结点
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * 头结点
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * 链接新元素到头部
     */
    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++;
    }

    /**
     * 链接新元素到尾部
     */
    void linkLast(E e) {
        // 记录在尾部添加元素前的尾指针
        final Node<E> l = last;
        // 创建新结点
        final Node<E> newNode = new Node<>(l, e, null);
        // 尾指针指向新节点
        last = newNode;
        // 如果链表还是空表,则头指针也指向新结点,否则将新结点链接到尾部
        if (l == null)
            first = newNode;
        else
            l.next = newNode;

        // 尺寸增加
        size++;
        modCount++;
    }

    /**
     * 在succ结点前插入元素e
     */
    void linkBefore(E e, Node<E> succ) {

        // 记录succ结点的前驱
        final Node<E> pred = succ.prev;
        // 生成新结点,并链接到succ结点的前驱和succ结点本身。
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 更新succ的直接前驱结点为新结点
        succ.prev = newNode;

        // 如果记录的succ结点的前驱为null,说明插入的位置位于头部,则让头指针指向新结点,否则更新其后驱为新结点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        // 尺寸增加
        size++;
        modCount++;
    }

    /**
     * 断开头部f结点的链接,
     */
    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;
        else
            // 非空表,则置空新的头结点元素的前驱
            next.prev = null;
        // 减少尺寸
        size--;
        modCount++;
        return element;
    }

    /**
     * 断开尾部l结点的链接
     */
    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;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 断开结点x,并返回其item
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        // 更新断开处的前驱
        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;
    }

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

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

    /**
     * 移除头结点元素
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    /**
     * 移除尾结点元素
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

    /**
     * 添加元素到头结点
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

    /**
     *  添加元素到尾结点
     */
    public void addLast(E e) {
        linkLast(e);
    }

    /**
     * 添加指定的元素到链表的尾部.
     * 和addLast()方法行为一致.
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * 获取指定位置的元素
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    /**
     * 更新指定位置的元素
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

    /**
     * 插入新元素到指定位置
     */
    public void add(int index, E element) {
        // 检查插入位置是否合法
        checkPositionIndex(index);

        // 如果是尾部,则直接链接到尾部,否则先查找到指定为的元素,然后在其前链接元素。
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    /**
     * 获取指定索引的元素
     */
    Node<E> node(int 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;
        }
    }

    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的Object[]数组    
    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

    // 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                    a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
    }

    private static final long serialVersionUID = 876323262645176354L;

    // Positional Access Operations 随机访问位置的方法
    // set     设置index位置对应的节点的值为element  
    // get     返回LinkedList指定位置的元素    
    // remove  删除index位置的节点 

    // Search Operations 搜索方法
    // indexOf
    // lastIndexOf

    // Queue operations. 队列方法
    // peek    返回第一个节点
    // element 返回第一个节点 
    // pool    删除并返回第一个节点
    // remove  删除并返回第一个节点
    // offer   添加指定元素到队列尾部

    // Deque operations  双端队列方法
    // offerFirst 添加元素到队列头部
    // offerLast  添加元素到队列尾部
    // peekFirst  返回队头元素
    // peekLast   返回队尾元素
    // pollFirst  返回并移除队头元素
    // pollLast   返回并移除队尾元素
    // push       添加元素到链表头部 
    // pop        移除链表头部的数据

    // 从LinkedList开始向后查找,删除第一个值为元素(o)的节点 
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点    
    // removeFirstOccurrence  
    // 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点 
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
    // removeLastOccurrence

    // 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)
    // listIterator

    // List迭代器
    // class ListItr
}

关于LinkedList的源码,给出几点比较重要的总结:

1, 从源码中可以看出,LinkedList的实现是基于双向链表的,始终有头指针指向第一个结点,有尾指针指向最后一个结点。

2,注意两个不同的构造方法。无参构造方法中没有做任何事情,包含Collection的构造方法,将Collection中的数据加入到链表的尾部后面。

3,在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。

4,LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。

5,源码中node(int index)方法,该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。

6,LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。

7,在源码中提供了栈,队列,双端队列的操作方法,因此可以作为栈和双端队列使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值