深入LinkedList的实现(三)--删除元素(下)

831 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

每天一小步,成功一大步。大家好,我是程序猿小白 gw_GW,很高兴能和大家一起学习每天小知识。

以下内容部分来自于网络,如有侵权,请联系我删除,本文仅用于学习交流,不用作任何商业用途。

摘要

本文主要介绍删除链表的第一个节点的方法以及实现原理。

建议大家参考我的上一篇文章,也是介绍链表的删除元素,因为太长怕读者看不下去因此就分开来发。

深入LinkedList的实现(三)--删除元素(上)

删除元素

poll()

Epoll() 获取并移除此列表的头(第一个元素)
 public E poll() {
     final Node<E> f = first;
     return (f == null) ? null : unlinkFirst(f);
 }

如果链表为空,就返回null,否则就调用unlinkFirst()删除第一个元素并返回。这个unlinkFirst()方法值得大家关注,因为只要是删除链表的第一个元素实际上调用的就是unlinkFirst()方法。

那咱们就来看一下它是怎么实现的。

 //解除第一个非空节点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;
 }

原理就是把first变为第一个节点的下一个节点(第二个节点),然后把第一个节点置为null。如果链表只有一个节点,就把最后一个节点last置为null,否则就把第二个节点的前驱节点置为null,并把长度size减1,修改次数加1,最后返回删除节点的值。

pollFirst()

EpollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null
 public E pollFirst() {
     final Node<E> f = first;
     return (f == null) ? null : unlinkFirst(f);
 }

这里不仅要想了,它和poll有什么不一样呢,都是要删除列表的第一个元素并且返回。观察源码我们发现,pollFirst调用的同样也是unlinkFirst()方法,所以实际上这两个方法没什么不一样。

pop()

Epop() 从此列表所表示的堆栈处弹出一个元素。
 public E pop() {
     return removeFirst();
 }

看源码我们知道实际上pop()就是removeFirst()。下面我们再来分析removeFirst()方法。

remove()

remove有三个重载方法,分别是:

  • E remove() 获取并移除此列表的头(第一个元素)。
  • E remove(int index) 移除此列表中指定位置处的元素。
  • boolean remove(Object o) 从此列表中移除首次出现的指定元素(如果存在)。

首先来看第一个空参方法。

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

实际上remove就相当于removeFirst方法。而pop()也是removeFirst()方法,那我们就来看看如何实现的。

 public E removeFirst() {
     final Node<E> f = first;
     if (f == null)
         throw new NoSuchElementException();
     return unlinkFirst(f);
 }

如果链表为空就抛出异常,接着调用unlinkFirst方法,这个方法是不是很熟悉。对呀,poll方法和pollFirst方法中调用的就是unlinkFirst方法。 接着我们来看第二个含参方法。

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

观察源码我们发现其实也很简单,就是从第一个节点了开始遍历,一直找到和要删除的节点的值相等的节点,值可以为null,如果找到就调用unlink方法删除该节点,返回true,否则返回false。那我们就来看看unlink方法是如何实现的。

 E unlink(Node<E> x) {
     // assert x != null;
     final E element = x.item;
     final Node<E> next = x.next;
     final Node<E> prev = x.prev;
 ​
     if (prev == null) {
         first = next;
     } else {
         prev.next = next;
         x.prev = null;
     }
 ​
     if (next == null) {
         last = prev;
     } else {
         next.prev = prev;
         x.next = null;
     }
 ​
     x.item = null;
     size--;
     modCount++;
     return element;
 }

首先记录下当前节点的下一个节点和前一个节点。如果前一个节点是null说明要删除的是第一个节点,就把first置为next,否则就把前一个节点的next指向当前节点的next,然后让当前节点的前驱节点置为null。如果当前节点的下一个节点是null说明,当前节点是最后一个节点,就把last置为当前节点的前驱节点。即把last往前挪一位。否则就把前一个节点的next指向当前节点的next,然后把当前节点的后驱节点置为null,最后把该节点的值置为null,把长度size--,修改次数加1,返回删除节点的值。

最后我们来看第三个含参方法。

 public E remove(int index) {
     checkElementIndex(index);
     return unlink(node(index));
 }

我们来对源码进行逐句分析,首先调用了checkElementIndex方法。

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

checkElementIndex方法又调用了isElementIndex方法。

 private boolean isElementIndex(int index) {
     return index >= 0 && index < size;
 }

结合这三个方法我们不难发现,checkElementIndex方法是用来判断index是否合法,如果不合法就抛出异常。

然后再来看unlink方法,上面我们已经知道unlink方法是用来删除节点,在这里unlink又调用了node方法,那我们就来看它是干什么的,实际上我们已经可以大概猜到了,node方法就是根据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;
     }
 }

结果却是是这样,但是人家不愧是大佬,实现的很巧妙。首先判断,index是在中间节点之前还是在中间节点之后,如果是之前就从前往后找,如果是在中间节点之后就从后往前找。

removeFirst()

EremoveFirst() 移除并返回此列表的第一个元素。
removeFirst方法上面已经介绍过了,这里就不再介绍了。

removeFirstOccurrence()

booleanremoveFirstOccurrence(Object o) 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
 public boolean removeFirstOccurrence(Object o) {
     return remove(o);
 }

删除元素总结

clear() 删除所有元素。

poll(),pollFirst(),pop(),removeFirst(),removeLastOccurrence()方法实际上就是remove()方法。这些方法调用的都是unlinkFirst方法。

pollLast(),removeLast()方法实际上是一样的,都是调用unlinkLast()方法。

removeLastOccurrence()方法其实就是倒着的remove含参方法。 实际上removeFirstOccurrence就是remove的第二个含参方法。这里就不再详细介绍了。

结语

以上就是我对链表的删除元素的一些浅见,如有错误之处,欢迎掘友们批评指正。