List相关类源码解析之LinkedList

类图

一, 要分析 LinkedList, 我们最好先了解一些背景知识.

1, LinkedList 和ArrayList类似, 是 List的一个实现. 因此, 我们可以大概知道. LinkedList实现的功能和ArrayList 是类似的, 底层的结构不同
2, 链表的实现必须要有类似指针的对象, 指向下一个对象, 通过进入一个get方法, 可知. get首先获取的是一个 Node对象, 再从node对象获取我们存进去的元素, 所以, LinkedList每一个元素实际上是包装了我们存储的元素.
3, 从Node类中, 我们可以知道, Node有 next 和 prev , 所以 LinkedList实现的是一个 双向链表. 而链表还有一个特性, 需要指针来指明位置. (此处使用 first和last)否则无从访问
4, 当只有一个节点时, header即是头节点也是尾节点.即指明位置的指针first和last指向同一个元素.

二, 源码.

1, 构造函数. LinkedList基本是空构造, 没什么说的了. 要注意的是 LinkedList的节点Node. 创建一个新节点, 必须指定, 上一个节点和先一个节点.

   /**
    * 创建LinkedList
    */  
    public LinkedList() {
    }
    /**
    * 创建包含集合c的 LinkedList
    */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
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, 主要属性

    transient int size = 0;  //元素个数

    /**
     * 头指针
     */
    transient Node<E> first;

    /**
     *  尾指针
     */
    transient Node<E> last;

和ArrayList类似, LinkedList 的父类继承了AbstractList, 因此也有一个 modCount. 来进行快速失败.
3, 方法
add方法

    /**
     * 将元素添加到链表尾部,该方法实际上和 addLast相同. 
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    /**
     * 将 e 包装为Node添加到链表尾部. 
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);  //创建新的Node, 将原先的尾部节点作为该节点的前节点. 下一个节点为null 
        last = newNode;    //尾部节点修改为节点. 
        if (l == null)     //如果尾部节点是空的,即没有尾部节点. 说明. 添加前链表是空的. 因此把 first 指针 指向 该节点. 
            first = newNode;
        else   //如果不为空. 建立双向连接, 把前节点的next指向新节点.(新节点指向前节点在new Node时就建立了)
            l.next = newNode;
        size++;      //长度+1
        modCount++;     // 修改次数+1
    }

我们再来看看 addFirst方法.

    /**插入一个元素到链表头部
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

    /**
     * 把 e 作为头节点插入到链表. 
     * 基本逻辑. 把原链表.头节点的  prev 指针指向新节点. 
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);  //创建新节点, 并把新节点的 next指针指向. 原连表的头结点. 
        first = newNode;   // 头指针指向新节点. 
        if (f == null)      // 如果原头指针是null, 即没有头部节点, 说明, 添加元素前链表是空. 
            last = newNode;   // 把尾指针也指向 新节点. 
        else          //否则,说明原链表不为空, 那么. 建立原节点到新节点的指针,(新节点到原节点的指针在创建 new Node时就建立了)
            f.prev = newNode;
        size++;    //长度+1
        modCount++;   //修改次数+1
    }

我们再来看看, add(int index, E e) 方法, 和addFirst和类似的, 也是建立连接即可. 和 ArrayList不同的是. 由于通过指针而非数组(游标)建立连接.因此 linkedList无须考虑扩容.

    public void add(int index, E element) {
        checkPositionIndex(index);   //检查位置是否合法. 即index>=0和 index<=size

        if (index == size)    // 如果index =size , 那么即addLast
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    /**
     * 返回一个 index 位置下的链表节点. 
     */
    Node<E> node(int index) {
       if (index < (size >> 1)) {  //优化效率. 按照逻辑, 如果 index 小于 size的 1/2, 那么从头指针开始取这个节点比较快, 否则, 从尾指针开始取这个节点比较快.  
            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;
        }
    }
    /**
     * 把E插入到 succ几点前.和addFirst类似的. 
     */
    void linkBefore(E e, Node<E> succ) {
        final Node<E> pred = succ.prev;  //取的succ节点的 前一个节点. 
        final Node<E> newNode = new Node<>(pred, e, succ);  //  创建新节点, prev指针指向 原succ的前节点, next指针指向 succ节点. 
        //下面的代码就是 重建 succ的 prev指针和 和 prev(原链表succ的前节点)的 next指针, 不赘述     
        succ.prev = newNode;  
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

再看看 remove(int index)方法.

    public E remove(int index) {
        checkElementIndex(index);  //和add(index)类似, 检查index是否合法
        return unlink(node(index));  //node(index)返回某个位置的节点, 详细看add(index)
    }
    /**
     * 基本逻辑: 把 x 的前节点的next指向 x的后节点, 把x的后节点next指向前节点 . 并删除 x 的prev 和next指向(等待gc回收)
     */
    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) {  // 说明x是头节点
            first = next;
        } else {    //不是头结点,则把x前节点的 next指向, x的next. 并把x.prev断开. 
            prev.next = next;
            x.prev = null;
        }

        if (next == null) { // 说明x是尾节点
            last = prev;
        } else {   //不是尾结点,则把x后节点的 pre指向, x的prev. 并把x.next断开. 
            next.prev = prev;
            x.next = null;
        }

        x.item = null;    //item置为null
        size--;   //长度减-1
        modCount++;  //修改次数+1
        return element;  //返回删除元素.
    }

indexOf方法也是通过遍历的方式.

 public int indexOf(Object o) {
        if (o == null) {  //null时无法通过equal来比较.
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

contains(Object o) 和 indexOf方法, 以及 add和remove的重载方法. 基本大同小异,不再说明. 迭代器和ArrayList基本是一样的道理. 也比较简单, 此处也不再赘述.

简单总结.
  • 理解LinkedList最关键的一点其实就是理解**双向链表**,理解了双向链表, linkedList就基本没有什么问题。
  • LinkedList 通过指针连接.添加元素时没有扩容这个说法。 无容量限制.
  • 很多操作需要遍历链表. 并且通过指针取下一节点。 因此,查找来说效率比ArrayList较低,但是添加和删除时。 只需要断开指针并不需要移动数据, 效率比ArrayList较高。因此 对于大量的删除和修改,LinkedList比较合适。
  • get(int index) 和 remove(int index) 至多需要遍历1/2的链表长度,contains(E e)、 remove(E e)可能需要遍历全部。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值