对Linkedlist双向链表的添加与删除的解析
首先从new一个Linkedlist开始
LinkedList linkedList = new LinkedList<>();
走入源码:
public LinkedList() {
}
。。。。。看来新建一个Linkedlist什么都没有做,让我们先看看他默认的一些配置
transient int size = 0;
/**
* 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;
首先便是size,默认为0,接着就是默认为null的首结点和last结点。
这里看一下Node的源码:
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;
}
}
可以看出,元素e,前一个结点prev,后一个结点next
标准的双向链表
接着我们进行add操作看看
linkedList.add(1);
进入add方法的源码:
public boolean add(E e) {
linkLast(e);
return true;
}
依然很简单,接着走入linkLast的源码
通过linkLast这个名字可以看出,Linkedlist默认采用的是尾插法插入新元素
接着我们走入linkLast方法
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++;
}
首先令局部变量l等于last也就是尾结点,注意,当前是第一次添加,尾结点现在是null。
然后继续创建一个新变量newNode,使用构造方法对其进行构造
newNode节点前一个节点为原last节点,后一个节点为null,e即为传入的元素
接着让last = newNode,因为用的是尾插法,所以现在最后一个结点就是刚刚新建的这个newNode结点
然后进入判断,
当什么时候,原本的最后一个last结点为空呢?
只有一个结点时,那这个结点本身是同时属于首结点和尾结点的。
只有一个结点都没有的时候,变量l才能为空,这时可以判断只有一个元素
所以将结点newNode赋值给首结点first。
注:如果目前不只有一个结点,则进入else,即将原本的尾结点的下一个结点指向当前创建的这个newNode
至此,Linkedlist的添加操作到此结束。
Linkedlist 的删除操作
其实这倒是没什么好讲的,不过既然要讲,就一起讲了。
首先依然是看源码:
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;
}
当传入一个object时,先进入判断
这个判断由于操作都一样,就不展开讲了
如果变量o等于null,或者o不等于null
都使用for循环,从首结点first开始遍历,
终止条件为x==null(因为双向链表的尾结点的下一个结点为null)
每次都让x=x.next,也就是一个一个结点的查询,由每个结点指向的下一个结点查询
然后就是如果查询到的值等于null(这是第一个判断
或者等于传入的变量o(第二个判断
就执行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;
}
这下看上去有点长,不过实际上都是些if判断占的位置
让我们开始分析
首先依然是赋值,注意,传进来的是上一个操作所查询到的结点x
然后赋值element为x的数据
next为x的下一个结点
prev为x的上一个结点
然后依然是进入判断
如果prev是空,证明啥?
没有比他更前的了,那当然就是头结点啦
那x是我们要删掉的结点
所以他的下一个结点现在开始接受组织的重任,成为首结点
如果要删除的结点不是首结点呢?
那就将它前面一个结点的后面一个结点,连接到删除结点的后面
听起来有点拗口,那就画个图把
线的双箭头代表这是双向的
将它前面一个结点的后面一个结点,连接到删除结点的后面
这样操作后就变成了
现在第一个结点不指向要删除的结点了,而是直接指向第三个结点
同时要删除结点本身需要指向前一个结点,现在也不需要了,所以线删掉了,直接让其指向null。
接着要执行第二个判断了
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
依然是如果现在要删除的结点后面是null了
那证明这个就是尾结点嘛
那就直接把这个结点的前一个结点变成尾结点就ok了不是
否则的话就要
将下一个结点的前一个结点,连接上前一个结点
同时将本身指向下一个结点置空为null。
依然是画图来看
这时候我们发现,这时删除这个结点就不需要考虑了
它已经被孤立了
x.item = null;
size--;
modCount++;
return element;
因此,直接将其置空
然后进行减去size的操作
就可以返回啦
这里返回的element在直接输入元素进行删除时是用不到的
只有你输入下标进行删除时,才有用
是的,Linkedlist是有下标的
当然,Linkedlist毕竟是链表而不是数组
所以他的下标其实也只是方便进行二分查找的
例如如果总长度为10
如果输入get(3)
就会进行判断,小于5,所以从首结点开始遍历
如果大于五假如是7,
那就会从尾结点进行遍历
总结
关于Linkedlist的添加与删除解析就到这了,链表的数据结构还是挺基础的知识点,
本质上也不难,如果有什么错误,欢迎提出,我会加以改正(错字咱能理解就行哈