在分析LinkedList的源码以前,先看一下ArrayList在数据结构中的位置,常见的数据结构按照逻辑结构跟存储结构能够作以下划分:
node
先看看源码的注释:安全
从注释中能够看出,LinkedList是一个双向非循环链表,而且实现了Deque接口,仍是一个双端队列,因此比ArrayList要复杂一些。bash
在分析LinkedList以前咱们先复习一下链表这种数据结构数据结构
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是经过链表中的指针连接次序实现的。链表由一系列结点(链表中每个元素称为结点)组成,结点能够在运行时动态生成。每一个结点包括两个部分:一个是存储数据元素的数据域,另外一个是存储下一个结点地址的指针域。函数
链表按照指向能够分为单向链表跟双向链表,也能够按照是否循环氛分为循环链表跟非循环链表。
源码分析
双向循环链表跟单向循环链表能够进行类比,只是把head节点的pre指针跟tail节点的next指针分别指向tail跟head的数据区域而已。ui
先看一下ArrayList的继承关系this
跟ArrayList的区别在于LinkedList实现了Deque这个接口,Deque则继承自Queue这个接口,因此LinkedList可以进行队列操做,其他的实现跟ArrayList基本同样,再也不多说,下面开始分析LinkedList的源码。spa
//序列化
private static final long serialVersionUID = 876323262645176354L;
transient int size = 0;//元素个数
transient Node<E> first;//head结点
transient Node<E> last;//tail节点
//内部类节点
private static class Node<E> {
E item;存储的数据
Node<E> next;//next指针,指向下一个数据
Node<E> prev;//pre指针,指向上一个数据
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}复制代码
public LinkedList() {
}复制代码
当咱们经过此构造方法进行初始化LinkedList的时候,实际上什么都没作,此时只有一个Node,data为null,pre指向null,next也指向null。线程
//调用addAll
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//紧接着调用addAll(size, c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//这个方法比较关键,由于不论是初始化,仍是进行添加,都会调用此方法,下面重点分析一下
public boolean addAll(int index, Collection<? extends E> c) {
//检查index是否合法
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//初始化两个Node,保留下一个节点,当集合添加完成以后,须要跟此节点进行链接,构成链表
Node<E> pred, succ;
//插入的时候就是分两种,一种是从尾部插入,一种是从中间插入
if (index == size) {
//在尾部插入
succ = null;//null值做为后面链接的一个标志
pred = last;//将pred指向上一个节点也就是tail节点
} else {
//从中间插入
succ = node(index);
pred = succ.prev;
}
//遍历集合,按照顺序依次插入相应的元素
for (Object o : a) {
@SuppressWarnings("unchecked")
E e = (E) o;
//初始化一个节点并进行赋值
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//头结点为空,说明是空链表
first = newNode;
else
//Node个数>0,当前指针指向新的节点
pred.next = newNode;
//移到下一个节点
pred = newNode;
}
//链表添加完毕,开始从断开的地方进行链接
if (succ == null) {
//尾部插入进行链接,此时last须要从新赋值,即为pred节点
last = pred;
} else {
//中间插入,直接讲集合的最后一个节点跟以前插入点后的节点进行链接就好
pred.next = succ;将当前Node的next指针指向下一个节点
succ.prev = pred;//将下一个节点的pre指向pre
}
size += numNew;
modCount++;
return true;
}复制代码
结合图形来理解一下
稍微总结一下,这个addAll实际上就是先把链表打断,而后从断的左侧进行添加一些元素,添加完成以后再将链表进行链接起来,恩,就是这个样子,概括一下就是:
经过查看实际上有不少,这里就不一一贴出来了,最终调用的都是这几个方法:
private void linkFirst(E e) {
//拿到头结点
final Node<E> f = first;
//初始化一个结点,也便是新的头结点
final Node<E> newNode = new Node<>(null, e, f);
//将新节点赋值给头结点
first = newNode;
//头结点为空
if (f == null)
//则至关于此时只有一个节点,尾节点也是头结点
last = newNode;
else
//将原先的头结点的pre指针指向新的头结点
f.prev = newNode;
size++;
modCount++;
}复制代码
void linkLast(E e) {
//拿到尾节点
final Node<E> l = last;
//初始化一个Node,也就是新的尾节点
final Node<E> newNode = new Node<>(l, e, null);
//将新的尾节点赋值给last
last = newNode;
//尾结点为空
if (l == null)
//此时只有一个节点,因此当前节点便是头结点也是尾节点
first = newNode;
else
//将原先的尾节点指向如今的新的尾节点
l.next = newNode;
size++;
modCount++;
}复制代码
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//拿到要插入的元素以前节点
final Node<E> pred = succ.prev;
//初始化一个节点
final Node<E> newNode = new Node<>(pred, e, succ);
//将下一个节点的头指针指向插入的节点
succ.prev = newNode;
if (pred == null)
//若是此时只有一个节点,那么它既是尾节点也是头结点
first = newNode;
else
//将插入的节点跟前一个节点进行链接
pred.next = newNode;
size++;
modCount++;
}复制代码
有以下几个方法
跟add操做相对应,也只是改变相应的链表的指向而已,咱们选择一个来看看:
public E remove(int index) {
checkElementIndex(index);//检查删除的索引值
return unlink(node(index));//删除节点
}
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 {
//将删除节点的前一个节点的next指向后一个节点
prev.next = next;
x.prev = null;
}
if (next == null) {
//尾结点,删除以后,尾节点前移
last = prev;
} else {
//将删除节点的后一个节点的pre指向前一个节点
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}复制代码
说到底仍是在改变Node节点的指向而已
public E set(int index, E element) {
checkElementIndex(index);//检查索引
Node<E> x = node(index);//拿到须要修改的那个节点
E oldVal = x.item;//拿到修改的节点的值
x.item = element;//进行修改
return oldVal;
}复制代码
public E getFirst() {
final Node<E> f = first;//拿到head节点
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;////拿到tail节点
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//获取某一个索引的节点
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}复制代码
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == null) {
//遍历查找
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}复制代码
没有什么好说的,就是遍历查找而已,这里会发现,LinkedList的查找很低效,须要遍历整个集合。
public void push(E e) {
addFirst(e);
}复制代码
public boolean offer(E e) {
return add(e);
}复制代码
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}复制代码
public E pop() {
return removeFirst();
}复制代码
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}复制代码
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}复制代码
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}复制代码
上面都是关于队列的一些操做,用链表也能够实现,并且操做比较简单,能够看作是队列的一种链表实现方式。