小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
每天一小步,成功一大步。大家好,我是程序猿小白 gw_GW,很高兴能和大家一起学习每天小知识。
以下内容部分来自于网络,如有侵权,请联系我删除,本文仅用于学习交流,不用作任何商业用途。
摘要
本文主要介绍删除链表的第一个节点的方法以及实现原理。
建议大家参考我的上一篇文章,也是介绍链表的删除元素,因为太长怕读者看不下去因此就分开来发。
删除元素
poll()
| E | poll() 获取并移除此列表的头(第一个元素) |
|---|
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()
E | pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null。 |
|---|
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
这里不仅要想了,它和poll有什么不一样呢,都是要删除列表的第一个元素并且返回。观察源码我们发现,pollFirst调用的同样也是unlinkFirst()方法,所以实际上这两个方法没什么不一样。
pop()
| E | pop() 从此列表所表示的堆栈处弹出一个元素。 |
|---|
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()
E | removeFirst() 移除并返回此列表的第一个元素。 |
|---|---|
| removeFirst方法上面已经介绍过了,这里就不再介绍了。 |
removeFirstOccurrence()
boolean | removeFirstOccurrence(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的第二个含参方法。这里就不再详细介绍了。
结语
以上就是我对链表的删除元素的一些浅见,如有错误之处,欢迎掘友们批评指正。