List集合之LinkedList(一)通过源码看特性

本文详细介绍了Java中的LinkedList数据结构,包括其内部的双向链表设计、节点结构、元素插入、删除、查找等操作。LinkedList的元素通过双向链接实现顺序性,同时提供了模拟栈和队列的功能。文章还探讨了LinkedList的动态容量、有序性和元素重复性等特性。
摘要由CSDN通过智能技术生成

思维导图

(以下源码来自jdk1.8.0_282) 

数据结构

我们知道LinkedList就是为了模拟双向链表的数据结构而设计的

双向链表的模型设计

Java没有直接单独提供双向链表数据结构的实现。所以LinkedList在实现特性功能前,需要先实现双向链表的数据结构。

双向链表数据结构的关键实现是 “链接节点” 设计。且该链接节点需要体现双向性,即承上启下的特性。

    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中“链接节点”代码设计如上,结构类似如下

其中item是存储的具体元素。

prev是指向该节点的上一个节点

next是指向该节点的下一个节点

双向链表双向性的体现

可能单单一个节点还不能体现双向性,下面给出三个节点的链接图

从上图可以看出

Node1的pre1指向了Node0,Node0的next0指向了Node1。

Node1的next1指向了Node2,Node2的pre2指向了Node1。

 

而双向性体现在

即可以让Node0通过next0找到Node1,

也可以让Node1通过prev1反向找到Node0

 

相反地,在单向链表中

Node0可以找到Node1,但是Node1却不能找到Node0

双向链表的first,last节点设计

为了保证双向链表不形成一个环结构,所以双向链表必须有头和尾。所以需要将头和尾节点单独定义出来,如下代码

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

从first和last的注释可以看出:

first只有两种情况,

     1.没有头节点和尾节点,即为空链表

      2.有头节点,且头节点的prev是null

last也只有两种情况:

     1.没有头节点和尾节点,即为空链表

      2.有尾节点,且尾节点的next是nul

这里没有说

       有头节点,且头节点的prev是last

或者有尾节点,且尾节点的next是first。

所以LinkedList的底层数据结构 准确来说是一个  双向不循环链表。

构造器

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


    /**
     * 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();
        addAll(c);
    }

LinkedList有两个构造器,一个无参构造器,一个带参Collection c构造器。

当我们使用无参构造器创建LinkedList对象时,该对象只是初始化了first=null,last=null

当我们使用LinkedList(Collection c)创建对象时,内部调用了addAll(Collection c)方法,具体到addAll(Collection c)方法中讨论。

 

插入元素

常用方法

boolean add(E e)

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * 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) // 我们知道当l为null时,即老的last为null,则说明对应的prev也是null,即链表是一个空链表。
            first = newNode;// 若为空链表,则插入的节点就是链表的唯一节点,既是last,也是first
        else
            l.next = newNode;// 若为非空链表,则需要将老的last节点的next指向新的last节点
        size++; // size是LinkedList的成员属性,初始值为0,加入一个元素后,size++为1
        modCount++;// modCount变更可知该操作是集合结构化修改操做
    }

从上面代码可以看出 add(E e)方法是在链表尾部插入一个节点。

 

下面画图演示在一个空链表中add(E e)

结合代码和图示:

第一步:先创建新节点e,e节点的prev指向了last,next指向了null

第二步:由于新节点e是插入到链表尾部,所以e节点是新的last节点

第三步:判断老的last节点是否为null,即插入e节点前,链表是否为空链表

             若为空链表,则将first也指向e节点。

              即当前链表只有一个节点,既是first 也是last

 

下面画图演示在一个非空链表中add(E e)

第一步:创建新节点e2,其中prev2指向last,next2指向null

第二步:由于e2节点是插入到链表尾部,所以e节点是新的last

第三步:判断老的last,即e节点是否为null,

             若不为null,则链表不是空链表,则将e节点的next指向新的last,即e2

 

上述两种情况就是 linkLast(E e)的实现。

 

void add(int index, E element)

    /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

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

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * Tells if the argument is the index of a valid position for an
     * iterator or an add operation.
     */
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

    /**
     * Inserts element e before non-null Node 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);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

add(int index, E element)方法是将element加到链表的index位置

首先链表结构不像数组(每个元素都有索引)一样,链表节点是没有索引的。所以这里的index并不是指元素的索引。

但是链表节点是有顺序性的,我们可以按照从first节点到last节点排序号,first节点就是序号0,last节点就是序号size-1 (size>=1时)。所以可以将index理解为序号。

那么链表元素是如何实现排序的呢?通过之前的图示我们知道  上一个节点的next指向下一个节点,下一个节点的prev指向上一个节点。

所以 first 的 index =0 

则   first.next 的 index= 1

则   first.next.next 的 index = 2

...

则   last为 index = size -1

同样的,LinkedList是双向链表,具有双向性

所以  last 的 index = size -1

则     last.prev 的 index = size - 2

则     last.prev.prev 的 index = size - 3

...

则  first 的 index = 0

可以看出LinkedList底层双向链表查找对应index的元素,不像数组那样具有随机访问性,而是必须从first->last 或 last->first一个一个查询,所以LinkedList查询效率低。

而为了提升一点查询效率,Java大佬们使用了二分法查询。即:

若 index < size*0.5,则说明该索引在链表的前半部分,则可以从first->last方向查询

若index >= size*0.5,则说明该索引在链表的后半部分,则可以从last->first方向查询

具体代码实现如下

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

以上解释了LinkedList的元素索引的实现。下面继续讨论add(int index, E element)方法

add(int index, E element)方法可以分为三种情况讨论

当index=0时,说明element将要被插入到链表头部,此时只关注element和first

当index=size时,说明element将要被插入到链表尾部,此时只关注element和last

当 0<index<size 时,说明element将要被插入到链表中间,此时需要关注element和前后两个节点

Java这里将 index = size 归为了 linkLast(element),关于linkLast实现和add(E e)方法中实现一致,不再赘述。

               将 index<size 归为了 linkBefore(element,node(index)) 

下面图示linkBefore(E e, Node<E> succ)实现,其中e是将要被插入到index位置新节点的元素,succ是插入前,链表中index位置的节点

从add(e,0)图示可以看出:

第一步:创建newNode节点,newNode.prev指向了Node0.prev,newNode.next指向了Node0

第二步:解开Node0与前一个节点的连接,即Node0.prev改为指向newNode,将Node0作为newNode的下一个节点

第三步:由于Node0已经不是头节点了,所以first改为指向newNode

从add(e,1)图示可以看出:
第一步:创建newNode节点,newNode.prev指向了Node1.prev,newNode.next指向了Node1

第二步:解开Node1和前一个节点的连接,即Node1.prev改为指向newNode,将Node1作为newNode的下一个节点

第三步:由于Node1本来就不是头节点,所以还需要解开Node1上一个节点Node0的next连接约束,即将Node0.next指向newNode

 

boolean addAll(Collection<? extends E> c)

    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the specified
     * collection's iterator.  The behavior of this operation is undefined if
     * the specified collection is modified while the operation is in
     * progress.  (Note that this will occur if the specified collection is
     * this list, and it's nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

通过源码可以看到addAll(Collection c)内部是调用另一个重载方法addAll(size,c)

可以简单看出addAll(Collection c)是将Collection c中的元素存储到LinkedList的size位置,即当前链表的尾部。

具体Collection c中的元素按何种顺序存入,请看下面allAll(int index,Collection c)的实现。

 

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

   /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element
     *              from the specified collection
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();// 将c集合转为数组a
        int numNew = a.length;
        if (numNew == 0)// 如果a.length==0,表示集合c是个空集合
            return false;// 返回false表示添加失败

        Node<E> pred, succ;// 定义两个节点 插入节点前面的节点pred,插入节点后面的节点succ
        if (index == size) {// 如果index==size,说明将c集合元素插入到当前链表尾部
            succ = null;// 由于插入节点是插入到尾部,所以succ不存在
            pred = last;// 由于插入节点是插入到尾部,所以pred是last节点
        } else {// 如果插入节点插入到当前链表中间或头部
            succ = node(index);// 则需要获取插入位置index的节点作为 插入节点后面的节点
            pred = succ.prev;// 将succ前面的节点作为  插入节点的前一个节点
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);// 定义插入节点
            if (pred == null) // 如果插入节点前面的节点是null,则说明插入到了链表头部
                first = newNode;// 则插入节点是第一个节点,即为头节点first
            else //如果插入节点前面的节点不是null,则说明插入位置不是头部
                pred.next = newNode; 则插入节点的前一个节点的next改为指向插入的节点
            pred = newNode; // 将插入节点作为最小的pred,而后循环此流程
        }

        if (succ == null) { // 如果插入节点是插入到尾部
            last = pred;//所以最后一个插入节点就是尾节点last
        } else {// 否则说明插入位置不是尾部,则最后一个插入节点的next要指向succ,且succ要指向最后一个插入节点
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;// LinKedList的元素数量增加c集合元素个数
        modCount++;// 集合已被结构化修改
        return true;// 返回插入成功
    }

addAll(int index, Collection c)的实现思路和add(int index,E e)差不多,都分为两种情况:

1.是在链表尾部插入c集合元素,即插入位置index=size

2.是在链表头部或中间插入c集合元素,即插入位置0=<index<size

只考虑主体逻辑,该方法的实现关键是定义了两个特殊节点pred,succ。

pred指代  新节点的插入位置的前面一个节点

succ指代  新节点的插入位置的后面一个节点

当情况1时,即插入在当前链表尾部时,即pred是当前链表的last节点,succ不存在

当情况2,插入到当前链表头部时,即pred是null,succ是first节点

当情况2,插入到当前链表中间时,即pred是node(index-1),succ是node(index)

而这里的新节点可以理解成一个双向链表段,它会按照c.toArray()的数组元素顺序组成一个双向链表段。

 

void addFirst(E e)

    /**
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

    /**
     * Links e as first element.
     */
    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++;
    }

插入元素到链表头部,方法名友好。体现了LinkedList的底层是个双向不循环链表。

 

void addLast(E e)

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     */
    public void addLast(E e) {
        linkLast(e);
    }

和add(E e)实现相同,只是方法名更加友好。体现了LinkedList的底层是个双向不循环链表。

 

特性证明

集合元素元素类型可以不一致,但是必须是引用类型。

该特性可以从节点Node(Node pre,E e,Node next)定义可知,其中e是集合元素,E是指e的类型,E是泛型,如果不指定泛型的话,那E就是默认的Object类型。而所有引用类型都可以向上转型为Object类型。

 

集合的容量是动态的

LinkedList的底层是双向链表。

它和ArrayList存储机制不一样,ArrayList底层是数组,数组的特点是使用前必须初始化,即数组使用前必须要确定长度。

ArrayList的动态容量实现是创建新数组来实现扩容。

而链表使用前不需要确定长度,或容量,链表的容量是真正的动态的。所以LinkedList的容量是动态的。

 

LinkedList集合元素是有序的

LinkedList的集合元素的有序性体现在

上一个节点的next指向下一个节点

下一个节点的prev指向上一个节点

 

LinkedList集合元素可以重复

LinkedList的add操作没有做添加元素的重复性检查操作,所以LinkedList的集合元素是可重复的。

 

获取元素

常用方法

E get(int index)

    /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * Tells if the argument is the index of an existing element.
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

 E get(int index)用于获取链表中index位置的节点的元素。

关于链表节点的索引,在前面已经讨论过了。链表节点不具备索引能力,体现在LinkedList上的索引能力是通过Node node(int index)方法实现的。

Node node(int index)方法,将first节点当成索引0,first.next节点当成索引1,first.next.next节点当成索引2,....,last节点当成索引size-1,

                                 也可以将last节点当成索引size-1,last.prev节点当成size-2,last.prev.prev节点当成索引size-3,...,first节点当成索引0

Node node(int index)方法采用二分法查询,提升了查询效率。

 

E getFirst() 

    /**
     * Returns the first element in this list.
     *
     * @return the first element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

getFirst()方法用于获取链表first节点的元素。如果没有first节点,则抛出无此元素异常。

 

E getLast()

    /**
     * Returns the last element in this list.
     *
     * @return the last element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

getLast()方法用于获取链表last节点的元素。如果没有last节点,则抛出无此元素异常。

 

特性证明

LinkedList集合元素有索引

LinkedList底层是双向链表,双向链表节点本身没有索引概念,但是有顺序概念。

所以LinkedList将底层双向链表的first节点当成索引0节点,后面节点(前面节点.next)索引依次加1,直到last节点的索引为size-1。

需要注意地是:LinkedList根据索引访问集合元素,并不像ArrayList那样访问具有随机性。LinkedList访问集合元素只能从first->last方向一个个找,或者从last->first方向一个个找。

这更加证明了LinkedList的元素索引是伪索引。

 

修改元素

常用方法

E set(int index, E element)

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

set(int index, E element)方法就是简单地将index索引处节点的元素改为element。

 

删除元素

常用方法

E remove()

    /**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E remove() {
        return removeFirst();
    }

 remove()方法用于删除LinkedList底层双向链表的first节点

内部实现是调用removeFirst()方法,具体实现请看removeFirst()

 

E remove(int index)

    /**
     * Removes the element at the specified position in this list.  Shifts any
     * subsequent elements to the left (subtracts one from their indices).
     * Returns the element that was removed from the list.
     *
     * @param index the index of the element to be removed
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {// x指要被删除的节点
        // assert x != null;
        final E element = x.item; // 取出x节点的item元素,后面删除成功后,会作为remove方法的返回值
        final Node<E> next = x.next;// 获取x节点的后一个节点next
        final Node<E> prev = x.prev;// 获取x节点的前一个节点prev

        if (prev == null) {//如果x的前一个节点是null,则说明x节点是first
            first = next;// 那么删除x节点后,x的后一个节点next就是新的first
        } else { //如果x的前一个节点不是null,则说明x节点不是first
            prev.next = next;// 那么删除x后,x的前一个节点的next指向改为x的后一个节点
            x.prev = null; // 将x节点的prev指向改为null,达到从链中彻底删除x节点的目的
        }

        if (next == null) {// 如果x的后一个节点是null,则说明x节点是last
            last = prev;// 则删除x之后,x的前一个节点就是新的last
        } else {// 如果x的后一个节点不是null,则说明x节点不是last
            next.prev = prev; // 则删除x节点后,x的后一个节点的prev指向改为x的前一个节点
            x.next = null; // 将x节点的next指向改为null,达到从链中彻底删除x节点的目的
        }

        x.item = null;// 此时x的prev,next都已经被设置为了null,需要将x.item也设置为null,那么x节点对象就会变成垃圾,等待被垃圾回收器回收
        size--;// 链表元素个数减一
        modCount++;// 集合对象发生结构化修改
        return element;//返回之前保存的被删除节点的元素
    }

remove(int index)方法的主体逻辑是unlink(node(index))。下面我们解析下unlink(Node x)方法实现:

unlink方法入参的节点x是需要被删除的节点。通过走读unlink代码发现,其思路是:由于要删除x节点,那么删除x节点后,x的前后两个节点就要建立联系。unlink的主要目的就是建立x前后两个节点之间的联系。

Java大佬们将x的前一个节点 定义为 x_prev, 将x的后一个节点 定义为 x_next。在建立x_prev和x_next节点联系前,我们需要搞清楚x_prev和x_next的具体场景

1. 当x节点是链表的唯一节点时,删除了x节点,那么链表就变成了空链表,即first=x_prev=null,last=x_next=null

2. 当x节点不是链表的唯一节点时,且x节点是头节点时,x_prev是null,删除x节点对x_prev无影响。而删除x节点后,x_next就是头节点。则需要删除x节点和x_next之间的联系,以及建立x_next和x_prev的联系,即x.next=null, x_next.prev=x_prev

3. 当x节点时尾节点时,x_next是null,删除x节点对x_next无影响。而删除x节点后,x_prev就是尾节点。则需要删除x节点和x_prev之间的联系,以及建立x_prev和x_next的联系,即x.prev=null, x_prev.next=x_next

4. 当x节点时中间节点时,则需要删除x节点和x_next,x_prev之间的联系,即将x.prev=null,x.next=null。然后需要建立x_next和x_prev的联系,即x_prev.next = x_next, x_next.prev = x_prev。

经过Java大佬的凝结提炼得到如下代码:

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

该代码的妙处是将上面情况3的修改凝练到了情况1,2中。上面这段代码

两个if体组合是情况1

两个else体组合就是情况4

一个if和另一个if的else组合就是情况2,3

说实话,这种代码思维 比 逻辑思维更加缜密。如果从逻辑思维考虑的话,很可能就漏掉情况1。

 

boolean remove(Object o)

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

remove(Object o)分两种情况:

1. o == null,此时删除LinkedList底层双向链表的第一个元素为null的节点。

2. o != null ,此时底层会调用o的equals方法去比较底层链表的每个节点的元素,匹配到的第一个元素节点删除

所以remove(Object o) 期望o的运行时类型重写了equals方法

E removeFirst()

    /**
     * Removes and returns the first element from this list.
     *
     * @return the first element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    /**
     * Unlinks non-null first node 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;
    }

removeFirst()和remove()实现一致,且remove()内部就是调用的removeFirst()方法。 

removeFirst()顾名思义就是删除头节点。

 

这里分为两种情况:

1.如果链表只有一个节点,则删除该节点(first)后,该链表为空链表,即last=null,first=null

2.如果链表有多个节点, 则删除该节点(first)后,first.next节点就是头节点,即 first.next.prev = null

 

boolean removeFirstOccurrence(Object o)

    /**
     * Removes the first occurrence of the specified element in this
     * list (when traversing the list from head to tail).  If the list
     * does not contain the element, it is unchanged.
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if the list contained the specified element
     * @since 1.6
     */
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

removeFirstOccurrence(Object o)该方法意思时删除第一次出现o元素的节点。内部就是调用的remove()方法。

 

E removeLast()

    /**
     * Removes and returns the last element from this list.
     *
     * @return the last element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }


    /**
     * Unlinks non-null last node 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;
    }

 removeLast()顾名思义就是删除链表尾节点。

 

boolean removeLastOccurrence(Object o)

    /**
     * Removes the last occurrence of the specified element in this
     * list (when traversing the list from head to tail).  If the list
     * does not contain the element, it is unchanged.
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if the list contained the specified element
     * @since 1.6
     */
    public boolean removeLastOccurrence(Object o) {
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

removeLastOccurrence(Object o)即删除链表中最后一次出现o元素的节点

实现也很简单

removeFirstOccurrence(Object o) 是 从first节点开始匹配删除

 removeLastOccurrence(Object o) 可以从last节点开始匹配删除,匹配到的符合要求的第一个节点被删除。

 

LinkedList模拟栈操作

常用方法

void push(E e)

    /**
     * Pushes an element onto the stack represented by this list.  In other
     * words, inserts the element at the front of this list.
     *
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @since 1.6
     */
    public void push(E e) {
        addFirst(e);
    }

我们知道push是栈数据结构常规操作,即压栈。将元素压入栈顶。

LinkedList对于push(E e)是内部调用addFirst(E e),即在链表头部插入节点,也能压栈的能力。

 

E pop()

    /**
     * Pops an element from the stack represented by this list.  In other
     * words, removes and returns the first element of this list.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this list (which is the top
     *         of the stack represented by this list)
     * @throws NoSuchElementException if this list is empty
     * @since 1.6
     */
    public E pop() {
        return removeFirst();
    }

我们知道pop是栈数据结构的基本能力,即弹栈。将栈顶元素弹出,并返回。

LinkedList对于pop()是内部调用removeFirst(),即删除链表头节点,并返回头节点的元素,也能实现弹栈能力。

 

E peek()

    /**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

我们知道peek是栈数据结构的基本能力,即不弹栈的情况下获取栈顶元素。

LinkedList对于peek()的内部实现是直接获取first.item,前提是first!=null

 

E peekFirst()

    /**
     * Retrieves, but does not remove, the first element of this list,
     * or returns {@code null} if this list is empty.
     *
     * @return the first element of this list, or {@code null}
     *         if this list is empty
     * @since 1.6
     */
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

和peek()方法实现相同,也是获取栈顶元素。

 

E peekLast()

    /**
     * Retrieves, but does not remove, the last element of this list,
     * or returns {@code null} if this list is empty.
     *
     * @return the last element of this list, or {@code null}
     *         if this list is empty
     * @since 1.6
     */
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

peekLast()准确来说是双向栈数据结构的能力。

双向栈的栈顶栈底元素都可以压栈弹栈。peekLast()就是在弹出栈底元素。

内部实现是直接获取last.item,前提是last!=null

 

E poll()

    /**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

poll()和pop()本质是一样的。都是弹出栈顶元素。 

 

E pollFirst()

    /**
     * Retrieves and removes the first element of this list,
     * or returns {@code null} if this list is empty.
     *
     * @return the first element of this list, or {@code null} if
     *     this list is empty
     * @since 1.6
     */
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

 pollFirst()和poll()实现一致,都是弹出栈顶元素。

 

E pollLast()

    /**
     * Retrieves and removes the last element of this list,
     * or returns {@code null} if this list is empty.
     *
     * @return the last element of this list, or {@code null} if
     *     this list is empty
     * @since 1.6
     */
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

pollLast()是弹出栈底元素。

 

boolean offer(E e)

    /**
     * Adds the specified element as the tail (last element) of this list.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Queue#offer})
     * @since 1.5
     */
    public boolean offer(E e) {
        return add(e);
    }

offer(E e)本质是add(E e),即addLast(E e),在栈底压栈。

 

boolean offerFirst(E e)

    /**
     * Inserts the specified element at the front of this list.
     *
     * @param e the element to insert
     * @return {@code true} (as specified by {@link Deque#offerFirst})
     * @since 1.6
     */
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

offerFirst(E e)也是栈顶压栈,本质和push(E e)一致

 

boolean offerLast(E e)

    /**
     * Inserts the specified element at the end of this list.
     *
     * @param e the element to insert
     * @return {@code true} (as specified by {@link Deque#offerLast})
     * @since 1.6
     */
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

offerLast(E e)是栈底压栈。

 

总结

LinkedList不仅可以模拟顺序栈,也可以模拟双向栈。

顺序栈常用的方法有:push(E e),pop(),peek()

双向栈常用的方法有:offer(E e),offerFirst(E e),offerLast(E e)

                                    poll(),pollFirst(),pollLast()

                                    peek(),peekFirst(),peekLast()

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿甘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值