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; //前置结点指针
}
}
单向链表:只有next,作为后置结点指针,指向下个结点。
双向链表:next和prev都有,一个指针指向后置结点,另一个指向前置结点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmhPqOw0-1624426661183)(容器源码/链表数据结构.png)]
Add方法:
- 将传入元素传递给linkLast方法:
public boolean add(E e) {
linkLast(e);
return true; //插入无需判断越界,所以直接返回true
}
- 进入linkLast方法:
- node是什么,前面刚开始介绍了。
- last也是一个Node类,初始值为空,表示上个结点:transient Node last;
- fist也是一个Node类,初始值为空,表示第一个结点:transient Node first;
void linkLast(E e) {
final Node<E> l = last; //l表示上个结点,如果为第一个则为null
final Node<E> newNode = new Node<>(l, e, null);
//传入一个新的结点后,前置结点就是链表以前的最后一个结点,后置结点为空
last = newNode; //最后一个结点变为新的结点
if (l == null) //如果l为空说明现在传入的是链表的第一个结点
first = newNode; //第一个结点变为新传入的结点
else //否则上个结点的下置结点变为当前传入的新节点
l.next = newNode;
size++; //大小+1
modCount++; //统计进行了多少次add和remove
}
第一个元素插入:
往后的元素插入:
Get方法
- 进入方法内部,主要先判断长度有没有越界,没有就取值出来。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
- 进入checkElementIndex方法内部:判断传入的index是否超过固有长度。
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 再进入isElementIndex:发现是拿add方法中的size和index。
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Remove方法:
- 进入方法内部,和get方法一样先判断是否越界,这里不再赘述。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));//传入索引查出结点,而后把结点放入unlink方法中
}
- 进入node方法,查看取出结点过程。
- index < (size >> 1):判断索引是大于还是小于长度的一半。
- 大于就从头遍历,找到目标结点的上一个结点的后置节点指针,根据指针得到对应结点。
- 小于就从尾遍历,找到目标结点的下一个结点的前置节点指针,根据指针得到对应结点。
- index < (size >> 1):判断索引是大于还是小于长度的一半。
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;
}
}
- 进入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--; //长度减少1
modCount++; //操作次数+1,remove和add都会增加modCount的次数
return element; //返回这个结点的值,以置空
}