Java集合:LinkedList详解
参考学习
- 此处膜拜大佬,五体投地
- https://www.pdai.tech/md/java/collection/java-collection-LinkedList.html
- https://blog.csdn.net/zxt0601/article/details/77341098
底层实现
双!向!链!表!
ok,简单复习一下什么是链表?
链表链表听名字就是知道是一个链子状的表格,像下面这样。
链表是一个非线性不连续的存储结构,它的数据单元最基本的可以由两部分组成,1是存放数据的空间,2是指向下一个元素的指针。这种数据结构的特点就是查找比较耗时,因为要从第一个元素开始一个个的向后遍历才可以,但是对于修改或者增加操作,相对于数组类型就比较简单快速,因为就是一个指针的变化而已。
简单说了一下链表,双向链表就是在普通链表的基础上对每个节点引入了一个指向上一个元素的指针,双向就体现在既可以向后找,也可以向前找。
在理解了双向链表的结构之后,我们来看看LinkedList的代码实现,它是如何定义双向链表的。
LinkedList维护了一个内部类,名为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;
}
}
然后了解下LinkedList的初始情况,通过代码可以看到,初始化情况下的LinkedList到底是个啥样子,LinkedList维护了3个成员变量,其中需要注意到的是first和last两个初始节点。
// 初始元素数量为0
transient int size = 0;
// 头结点
transient Node<E> first;
// 尾结点
transient Node<E> last;
构造器
LinkedList提供了一个空参构造函数(啥也没做的构造函数),和一个带参构造,且内容比较简单,阅读下源码:
// 0.啥也没干的空参构造函数
public LinkedList() {
}
// 1.传入一个Collection类型的带参构造函数
public LinkedList(Collection<? extends E> c) {
this();
// 调用addAll方法,其实学习过前面的ArrayList源码解析,看到这一步是十分亲切的
// addAll方法见名知意,猜都能猜出来,就是创建一个长度等于c长度的链表,再把c中的元素全部插入到这个链表中
// 具体addAll方法的细节,下面会进行详细的介绍
addAll(c);
}
LinkedList的构造方法比较简单,不做过多的介绍。还是和ArrayList解析的节奏一样,看完了结构与构造器后面我们就详细了解下LinkedList中那些常用api。
常用API分析
首先来看看上面已经出现过的addAll方法
addAll(int index, Collection<? extends E> c)
其实LinkedList提供了两个addAll方法,其中addAll(Collection<? extends E> c)本质上调用的就是addAll(int index, Collection<? extends E> c),所以我们合并到一起学习,逐行分析
// 0. addAll方法,传入一个Collection类型的参数
public boolean addAll(Collection<? extends E> c) {
// 其本质调用的还是下面的addAll(int index, Collection<? extends E> c)方法
// 注意这里传入的第一个参数是size,那这句话的意思就是将collection添加到linkedlist的末尾
return addAll(size, c);
}
// 1.核心addAll方法,两个参数,第一个指的时候需要插入的脚标,第二个代表要插入的值Collection类型
public boolean addAll(int index, Collection<? extends E> c) {
// checkPositionIndex = 检查 位置 索引
// 还记得ArrayList的越界检查吗,记住有index的地方就有这个方法,防止index超出list的合理范围
// 它的解析在下面
checkPositionIndex(index);
// 老朋友,又见面了,Collection.toArray方法,到处都有它的影子
Object[] a = c.toArray();
// 这一步,当需要添加的集合是一个空的集合,那就直接返回false,不和你多BB
int numNew = a.length;
if (numNew == 0)
return false;
// !!! 这里要注意了,出现了两个新朋友,pred和succ
// 这两个兄弟会频繁出现在LinkedList的源码中,现在我们还不知道他的意思代表什么
// 不过我可以剧透一下,pred代表某个元素的前一个元素,succ代表某个元素的后一个元素
// 那么某个元素是啥呢?要晕了,不急向下看
Node<E> pred, succ;
if (index == size) {
// 当index==size,意思就是当要插入的位置是list的末尾的时候
// 此时succ=null,pred=last(list的最后一个元素),这里可能有点点绕,举个栗子
// 有list: 1->2->3->4->5,index=5,那么有succ指向空,pred指向5
// 为什么要这样?其实我想你已经知道了,还记得index==size吗?
// 当我们要往末尾插入的时候,假设我们要插入的第一个元素就是“某个元素”
// 那么先定义好pred就是我们要插入的元素的前一个元素,就是5这个元素,就是last
// 而succ=null是因为我们就是往末尾插入的,末尾后面没东西了,那就是null
// 那么这里我们也能理解上面说的“某个元素”指的就是即将要被我们操作的那个元素
succ = null;
pred