跟踪LinkedList源码,通过分析双向链表实现原理,自定义一个双向链表

1.LinkedList实现的基本原理

LinkedList是一个双向链表,它主要有两个表示头尾节点的成员变量first  、last,因为有头尾两个节点,使其很方便分别从头尾操作数据。LinkedList通过内部类Node来保存元素 ,一个Node对象表示一个链表的节点,有多少个元素就需要同样个Node节点。如果要添加元素,则新建一个Node节点,保存这个元素,同时指定其前驱节点和后继节点的引用。若要删除一个元素,则将取消此元素对应的Node节点在链表中的前驱后继关系。

2.ListedList的实现关键 -——静态内部类Node

Node类有3个成员变量:

  1)表示 当前节点保存的元素item ,

 2)表示后继节点的引用next

 3)表示前驱节点的引用 prev

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

 Node类有这3个成员能够很清楚的表示自己的逻辑结构。首先能确定自己的位置,前驱后prev/继节next点的引用指明了当前节点在链表结构中的位置;另外成员变量item确定了当前节点要保存的元素。

3.ListedList的成员变量

其成员变量主要是表示链表头 尾节点的两个成员变量:头节点first    尾节点last

因为有头尾两个节点,可以很方便地从头部或从尾部这两个方向进行逻辑操作。

只用一个头节点也可实现单向链表,它的效率不高,因为只能从单向从头到尾遍历,不能根据实际情况选择更优的遍历方向。

    // 链表的所含元素个数
    transient int size = 0;

    // 链表的头节点
    transient Node<E> first;
    // 链表的尾节点
    transient Node<E> last;
    /*
     * 此属性在本类中没有,此属性是从父类AbstractSequentialList中继承而来
     * 链表结构被修改的次数,防止在迭代遍历过程中,修改链表结构
     */
    protected    int modCount = 0;

4 .其构造方法

构造方法有两个,一个默认构造方法,将初始化一个空链表;另一个带单参数的构造方法,将初始化后,此链表将包含参数c的所有元素

    public LinkedList() { } //头尾节点都是null的空链表

    //初始化后,包含集合c所有元素的链表
    public LinkedList(Collection<? extends E> c) { 
        this();
        addAll(c);//添加集合c所有元素
    }

5.常用的API

1). public boolean add(E e)   在尾部添加一个元素

此方法去调用了一个对链表尾部连接新元素的方法linkLast(E)

    public boolean add(E e) {
        linkLast(e);//在尾部链接一个新元素
        return true;
    }

此时去看看linkLast(E)的实现细节:

  在添加新元素时要注意特殊情况,即要考虑当前的链表为空、不含任何元素的可能,此时需要将当前新添加的节点设为头节点

   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++;//添加一个元素,链表结构被修改一次,modCount自加一
    }

2)public void add(int index, E element)  在指定下标处添加元素

    此方法有两个分支,若插入的下标位置等于链表元素个数,则可直接在链表尾部添加要插入的元素;而其它一般情况则调用linkBefore(E,Node)方法,指定的节点之前插入元素。

而linkBefore(E,Node)方法又调用了Node<E>  node(int)方法。

    public void add(int index, E element) {
        checkPositionIndex(index); //检查下标是否合法

        if (index == size)  //插入的位置index=size即可直接在链表尾部添加元素
            linkLast(element);
        else
            //在指定的节点之前插入元素
            linkBefore(element, node(index));
    }

先来看看node(int)方法是如何根据下标查找到下标对应的Node节点

    node(int)方法中有两个分支,根据index是否小于size的一半作为进入不同分支的条件。此处分支条件是用来确定待查找的节点 与头/尾节点的距离,若待查找的节点和头节点较近,就从头节点开始查找;若待查找的节点与尾节点较近,则从尾节点开始遍历查找。这主要是从遍历的效率角度考虑的。

    Node<E> node(int index) {
        //index作为循环遍历停止的条件
        if (index < (size >> 1)) { // 如果index小于size的一半,则从头部开始向后查找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next; //将当前节点的后继节点赋值给当前节点
            return x;
        } else {     // 如果index大于size的一半,则从尾部开始向前查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--) 
                x = x.prev;//将当前节点的前驱节点赋值给当前节点
            return x;
        }
    }

最后来看linkBefore(E,Node)如何在指定的节点前添加指定的元素

  其中也对插入位置的进行了判断,如果是在头部插入节点,则需要将头节点的引用更新为待插入节点。

    void linkBefore(E e, Node<E> succ) {
         //插入点节点succ的前驱节点pred
        final Node<E> pred = succ.prev;
        /*
         * 待插入的节点,
         * 此节点将取代插入点节点succ,插入点succ将后移一位
         * 此节点的前驱是插入点节点的前驱节点pred,其后继节点就是插入点节点succ
         */
        final Node<E> newNode = new Node<>(pred, e, succ);
        //插入点节点的前驱是待插入节点
        succ.prev = newNode;
        
        if (pred == null)
            /*
             * 插入点节点的前驱节点pred为为空,表明此时在头部直接插入节点
             * 那么链表更新后,当前的待插入节点就是头节点
             */
            first = newNode;
        else
            /*
             * 插入点节点的前驱节点pred的后继节点是待插入节点
             */
            pred.next = newNode;
        size++;
        modCount++;
    }

3) public E remove(int index)

 public E remove(Object o) 

5)public E set(int index, E element)  

6)public  E get(int index)  

猜你喜欢

转载自www.cnblogs.com/gocode/p/11519541.html