LinkedList源码解析

本文详细解析了LinkedList的内部结构、实现原理及常用方法。介绍了单向、双向链表的概念,并重点分析了LinkedList作为双向链表的特点。此外,深入探讨了LinkedList的构造方法及增删改查等核心操作。

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

LinkedList源码解析

1.LinkedList概述

要了解LinkedList,我们首先要看一下API(1.6)里面的解释:

List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。
insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

在这里我们简单介绍一下链表的概念。

  1. 单向链表
    单向链表的构成有两部分组成,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针。第一个节点的next指向第二个节点,依次类推,这样子就把数据链在一起了。
    如果要访问链表中一个元素,需要从第一个元素始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表。
    这里写图片描述
  2. 单向循环链表
    单向循环链表其实比较好理解,就是链表的头和尾链接到了一起,看图片很直观。
    这里写图片描述
  3. 双向链表
    双向链表其实也不难理解,就是在单向链表的基础上,新增加了一个pre指针,用来指向上一个节点。双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。双向链表的优势就是可以访问上一个节点。
    这里写图片描述
  4. 双向循环链表
    第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。
    这里写图片描述

看完了上面对链表的了解,接下来看一下具体LinkedList的继承关系

public class LinkedList<E>extends 
AbstractSequentialList<E>implements List<E>, Deque<E>, 
Cloneable, Serializable

可以看一下继承图
这里写图片描述
从结构上,我们还看到了LinkedList实现了Deque接口,因此,我们可以操作LinkedList像操作队列和栈一样~

2.LinkedList属性

这里写图片描述
上面的图片中黄色的就是属性。
其中first,和last就是头结点和尾节点。
size就是链表的长度,可以看出初始值尾0。
其实我们可以来看一下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至少是一个双向链表。

3.LinkedList构造方法

1.LinkedList()

构造一个空列表。具体代码如下:

   /**
     * Constructs an empty list.感觉这个没什么用☺
     */
    public LinkedList() {
    }
2.LinkedList(Collection< extends E> c)

接着我们看一下里面传一个集合如何处理?

/**
     * 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();//这里的this()其实指的就是无参构造LinkedList() 。
        addAll(c);//具体方法在add()方法区演示。
    }

4.LinkedList常用方法

1.add()方法
1.1 add(E e)

将指定元素添加到此列表的结尾。
此方法等效于 addLast(E)。

public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //我们在看看这个方法linkLast(e)
     /**
     * Links e as last element.
     */
    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++;
    }

linkLast(e)这个方法的作用就是把新添加的元素存放在元素的最后。先对first节点进行判断,如果为空,就存在第一个。不为空,就存放在last,并且赋值给上一个的next。

1.2 add(int index, E element)
    public void add(int index, E element) {
        checkPositionIndex(index);//有效位检验

        if (index == size)
            linkLast(element);//插入的位置和长度一样,就插入到最后一个。
        else
            linkBefore(element, node(index));
    }

这段代码的意思就是将指定的元素插入此列表中的指定位置。 将当前在该位置的元素(如果有的话)和任何后续的元素移到右侧(在其索引中增加一个)。
主要来看一下:linkBefore(E e, Node<E> succ)方法:

  void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        //就是把插入位置元素的前一个获取。在新建一个节点,pred节点就是
        //原来的节点,next节点存储的就是当前位置节点。这个就相当于把新元素插入到这个位置的前面。
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
            //查看当前位置是否为第一个元素,如果是,直接复制给first。
        else
            pred.next = newNode;
            //把新建节点赋给前一个元素的next
        size++;
        modCount++;
    }
1.3 addAll(Collection< extends E> c)

添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。

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

这个方法调用了addAll(int index, Collection< extends E> c) ,所以我们直接来看这个方法。

1.4 addAll(int index, Collection< extends E> c)

将指定 collection 中的所有元素从指定位置开始插入此列表。

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);//进行有效位校验

        Object[] a = c.toArray();
        int numNew = a.length;//获取数组长度
        if (numNew == 0)
            return false;//检测数组是否为空

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;//如果插入位置与长度一致,则添加在最后。
        } else {
            succ = node(index);
            pred = succ.prev;//否则添加到指定位置
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }
//通过foreach()遍历所有的元素,如果pred 不为空,则把新节点添加到pred 的next中

//下面的作用就是判断添加完数组之后后面后还有没有元素,如果有就把succ和之前的pred连接起来。
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
1.5 addFirst(E e)
public void addFirst(E e) {
        linkFirst(e);
    }
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++;
    }

这个很简单,就是把新节点给first,如果f不为空,则把新节点赋给f的prev,就是前一个节点。

1.5 addLast(E e)

将指定元素添加到此列表的结尾。等效于 add(E)。

public void addLast(E e) {
        linkLast(e);
    }
2.remove()方法
2.1 addLast(E e)
public E remove() {
        return removeFirst();
    }

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

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
3.set()方法
4.get()方法
5.其他方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值