LinkedList源码分析(jdk1.8)

简介:

之前没有看过关于链表的源码,习惯了数据结构,刚刚看的时候感觉还有点不适应,但是越看越有意思,哈哈。和ArrayList一样,无非是一些增删改查的功能实现,接下来还是围绕这些分析。

成员变量

首先还是一些成员变量,链表重要的成员变量还是比较少的。

    /**
     * 链表的长度
     */
    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;

构造器

LinkedList提供了两种构造器:空参构造器和有一个集合参数的构造器。

    /**
     * 空构造器
     */
    public LinkedList() {
    }

    /**
     * 指定集合的构造器
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        // 调用添加方法
        addAll(c);
    }

内部类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;
        }
    }

下面是addAll()方法,但是不建议之前没有看过链表相关源码先看这个方法,还是先看其他的方法,主要是方法太长了,打击看下去的积极性。下面是代码:

    /**
     * 从尾节点将指定集合中的元素添加到链表中
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    /**
     * 从指定节点将指定集合中的元素添加到链表中
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        // 检查索引是否正确
        checkPositionIndex(index);
        // 将指定集合转换成数组
        Object[] a = c.toArray();
        // 记录指定集合的长度,用于计算添加之后链表的长度。
        int numNew = a.length;
        if (numNew == 0)
            return false;
        // 记录插入的节点位置,pred(前一个节点),succ(后一个节点)
        Node<E> pred, succ;
        if (index == size) {
            // 即从为节点插入
            // 没有后一个节点,所以为null
            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)
                // pred 为null,说明是从头节点插入,所以将新创建出的节点设置为头节点
                first = newNode;
            else
                // 
                pred.next = newNode;
            // 将记录前一个节点的 pred 指向新节点,以便遍历插入下一个节点
            pred = newNode;
        }

        // 前面链接好了,肯定要链接后面的一部分节点
        if (succ == null) {
            // succ 为null,说明没有下一个节点,将遍历创建的最后一个节点设置为尾节点
            last = pred;
        } else {
            // 否则,将创建的最后一个节点和succ节点(也就是之前断开的后面的一部分链表的第一个节点)链接
            pred.next = succ;
            succ.prev = pred;
        }
        // 链表长度加上指定集合的长度
        size += numNew;
        // 操作记录数加1
        modCount++;
        return true;
    }

这里有一个node()方法,这个方法挺重要的,下面很多地方都用到了这个方法。

    /**
     * 寻找指定位置的节点
     */
    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;
        }
    }

下图帮助理解,这个过程
这里写图片描述

add()方法

    /**
     * 添加一个元素,从尾节点插入
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * 在指定位置插入指定元素,可以看到本质上还是调用的从尾节点插入的方法,或者从指定节点前面插入的方法。
     */
    public void add(int index, E element) {
        // 检查索引是否正确
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    /**
     * 将指定元素插入头节点
     */
    public void addFirst(E e) {
        // 实际插入头节点的操作
        linkFirst(e);
    }

    /**
     * 将指定元素插入尾节点
     */
    public void addLast(E e) {
        // 实际插入尾节点的操作
        linkLast(e);
    }

addFirst()这个方法本质上调用的是从头节点插入节点的方法,add()和addLast()本质上调用的是从尾节点插入节点的方法。

    /**
     * 将指定元素插入尾节点,这里居然没有用private修饰,具体原因我就不清楚了。
     */
    void linkLast(E e) {
        // 记录插入之前的尾节点
        final Node<E> l = last;
        // 创建新节点,prev=l(插入之前的尾节点),item=e,next=null
        final Node<E> newNode = new Node<>(l, e, null);
        // 将新节点赋值为尾节点
        last = newNode;
        if (l == null)
            // 如果插入之前尾节点为null(即没有元素),则将新节点也赋值为头节点
            first = newNode;
        else
            // 否则,将新节点赋值为插入之前的尾节点的 next
            l.next = newNode;
        // 链表的长度加1
        size++;
        // 操作记录数加1
        modCount++;
    }

看一下从头插入的方法吧,和从尾插入基本上是一样的。这些方法都被定义为private,所以具体调用的方法符合平常使用的方法名。

    /**
     * 将指定元素插入头节点
     */
    private void linkFirst(E e) {
        // 记录插入之前的头节点
        final Node<E> f = first;
        // 创建新节点,prev=null,item=e,next=f(插入之前的头节点)
        final Node<E> newNode = new Node<>(null, e, f);
        // 将新节点赋值为头节点
        first = newNode;
        if (f == null)
            // 如果插入之前头节点为null(即没有元素),则将新节点也赋值为尾节点
            last = newNode;
        else
            // 否则,将新节点赋值为插入之前的头节点的 prev
            f.prev = newNode;
        // 链表的长度加1
        size++;
        // 操作记录数加1
        modCount++;
    }

下图是linkFirst方法的图解;
这里写图片描述

下面是从指定位置之前插入节点的方法:

    /**
     * 在指定节点的前面插入指定元素,指定节点必须是 non-null (非 null)
     */
    void linkBefore(E e, Node<E> succ) {
        // 记录指定节点 succ 的前面一个节点
        final Node<E> pred = succ.prev;
        // 创建新节点,prev=pred,item=e,next=succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 将新节点赋值为指定节点 succ 的前一个节点
        succ.prev = newNode;
        if (pred == null)
            // 如果指定节点的prev为 null,说明指定节点 succ 为头节点,所以将新节点设置为头节点
            first = newNode;
        else
            // 否则,将新节点赋值为指定节点 succ 的前一个节点的 next (下一个节点)
            pred.next = newNode;
        // 链表的长度加1
        size++;
        // 操作记录数加1
        modCount++;
    }

下面是这个方法的图解:
这里写图片描述

remove()方法

分为删除头节点和删除为节点。

    /**
     * 删除头节点
     */
    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 E remove() {
        return removeFirst();
    }

本质上还是调用private的方法

    /**
     * 删除头节点
     */
    private E unlinkFirst(Node<E> f) {
        // 记录头节点的元素值
        final E element = f.item;
        // 记录头节点的下一个节点
        final Node<E> next = f.next;
        // 将头节点的item 和 next赋值为null ,帮助GC更快回收
        f.item = null;
        f.next = null; // help GC
        // 将头节点的下一个节点设置为头节点
        first = next;
        if (next == null)
            // 头节点的下一个节点为null ,证明原链表只有一个节点,设置尾节点为null
            last = null;
        else
            // 否则,将头节点的下一个节点的 prev(前一个节点) 赋值为null
            next.prev = null;
        // 链表的长度减1
        size--;
        // 操作记录数加1
        modCount++;
        // 返回被删除的头节点对应的元素值
        return element;
    }

    /**
     * 删除尾节点
     */
    private E unlinkLast(Node<E> l) {
        // 记录尾节点的元素值
        final E element = l.item;
        // 记录尾节点的前一个节点
        final Node<E> prev = l.prev;
        // 将尾节点的item 和 next赋值为null ,帮助GC更快回收
        l.item = null;
        l.prev = null; // help GC
        // 将尾节点的前一个节点设置为尾节点
        last = prev;
        if (prev == null)
            // 尾节点的前一个节点为null ,证明原链表只有一个节点,设置头节点为null
            first = null;
        else
            // 否则,将尾节点的前一个节点的 next(后一个节点) 赋值为null
            prev.next = null;
        // 链表的长度减1
        size--;
        // 操作记录数加1
        modCount++;
        // 返回被删除的尾节点对应的元素值
        return element;
    }

接下在就是删除指定节点的方法了

    /**
     * 删除指定节点
     */
    E unlink(Node<E> x) {
        // 记录指定节点的元素值
        final E element = x.item;
        // 记录指定节点的前一个节点
        final Node<E> next = x.next;
        // 记录指定节点的后一个节点
        final Node<E> prev = x.prev;

        if (prev == null) {
            // 如果指定节点的 prev 为null,说明指定节点为头节点,则将指定节点的下一个节点设置为头节点
            first = next;
        } else {
            // 否则,将指定节点的前一个节点的next 赋值为 下一个节点
            prev.next = next;
            // 将指定节点的prev赋值为null,加快gc
            x.prev = null;
        }

        if (next == null) {
            // 如果指定节点的 next 为null,说明指定节点为尾节点,则将指定节点的前一个节点设置为尾节点
            last = prev;
        } else {
            // 否则,将指定节点的下一个节点的prev 赋值为 前一个节点
            next.prev = prev;
            x.next = null;
        }
        // 将指定节点对应的元素赋值为null,加快GC
        x.item = null;
        // 链表的长度减1
        size--;
        // 操作记录数加1
        modCount++;
        // 返回被删除的指定节点对应的元素值
        return element;
    }

get()方法

获取头节点和尾节点,使用的是first,last这两个成员变量进行操作

    /**
     * 获取头节点的元素值
     */
    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;
    }

下面是从指定节点位置删除节点的方法,重点还是之前的寻找node位置的方法。

    /**
     * 获取指定位置的节点的元素值
     */
    public E get(int index) {
        // 检查索引是否正确 
        checkElementIndex(index);
        // 检查索引是否正确,并获取改节点的元素值返回
        return node(index).item;
    }

Set方法

    /**
     * 修改指定索引位置的节点的元素值
     */
    public E set(int index, E element) {
        // 检查索引是否正确
        checkElementIndex(index);
        // 检查索引是否正确
        Node<E> x = node(index);
        // 记录修改指点的元素值
        E oldVal = x.item;
        // 修改
        x.item = element;
        // 返回修改值之前的值
        return oldVal;
    }

其他方法

    /**
     * 判断元素是否存在于链表中
     */
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    /**
     * 获取指定元素在链表的位置
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            // 寻找第一个null元素的位置,null不能调用equals方法
            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;
    }

猜你喜欢

转载自blog.csdn.net/zhwyj1019/article/details/81227880