LinkedList
是实现了List
接口和Deque
接口的双向链表,实现了列表的所有操作,并且允许包括null
值的所有元素
组织机构
Collection
接口:是所有集合的顶层接口,Collection
接口代表一种规则,所有实现了Collection
接口的集合类都要遵循此规则,它声明了一些通用的集合操作方法Collection
与Map
的区别:Collection
和Map
最大的区别就是Collection
存储的是一组对象;Map
是以键值对
的形式对对象进行的管理。
List
接口:序列集合,它是一个元素有序(按照插入的顺序维护元素顺序)、可重复、可以为null的集合Queue
接口:队列集合,在集合的基础上添加了增删改查操作,并且队列默认使用FIFO(先进先出)规则。Queue
实现一般满足先进先出规则FIFO
,除了优先队列priority queue
和栈stack
,但是栈是FILO
(先进后出规则),优先队列自己定义了排序规则Queue
实现一般不允许插入null元素,但是LinkedList可以
Deque
接口:一个线性 collection,支持在两端插入和移除元素。Deque实际上是“double ended queue(双端队列)”的简称,通常读为“deck”。大多数Deque
实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。Cloneable
接口:实现了该接口的类可以显示的调用Object.clone()
方法,合法的对该类实例进行字段复制,如果没有实现Cloneable
接口的实例上调用Obejct.clone()
方法,会抛出CloneNotSupportException
异常。正常情况下,实现了Cloneable
接口的类会以公共方法重写Object.clone()
Serializable
接口:实现了该接口标示了类可以被序列化和反序列化AbstractCollection
抽象类:此类提供Collection
接口的骨干实现,以最大限度地减少了实现此接口所需的工作。AbstractList
抽象类:此类提供List
接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作。对于连续的访问数据(如链表),应优先使用AbstractSequentialList
,而不是此类。AbstractSequentialList
抽象类:LinkedList
的直接父类,此类提供了List
接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作。对于随机访问数据(如数组),应该优先使用AbstractList
,而不是先使用此类。从某种意义上说,此类与在列表的列表迭代器上实现“随机访问”方法get(int index)
、set(int index, E element)
、add(int index, E element)
和remove(int index)
的AbstractList
类相对立,而不是其他关系。
基础属性及方法
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable{
//长度
transient int size = 0;
//指向头结点
transient Node<E> first;
//指向尾结点
transient Node<E> last;
}
复制代码
如上源码中为LinkedList
中的基本属性,其中size
为LinkedList
的长度,first
为指向头结点,last
指向尾结点,Node
为LinkedList
的一个私有内部类,其定义如下,即定义了item(元素)
,next(指向后一个元素的指针)
,prev(指向前一个元素的指针)
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
中的元素为['A','B','C']
,其内部的结构如下图所示
LinkedList
底层是双向链表的实现
构造方法
在源码中,LinkedList
主要提供了两个构造方法,
public LinkedList()
:构造一个空列表public LinkedList(Collection<? extends E> c)
: 构造一个包含指定collection
中的元素的列表,这些元素按其collection
的迭代器返回
添加元素
通常我们会使用add(E e)
方法添加元素,通过源码我们发现add(E e)
内部主要调用了以下方法(在链表的最后添加元素)
/**
* 将指定的元素追加到此列表的末尾
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 链接e作为最后一个元素。
*/
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++;
}
复制代码
通过源码可以看出添加的过程如下
- 构造一个局部常量来记录当前末尾节点
- 构造新节点:新节点的
prev
指向l
,e
是待添加的元素,next
是null
- 将当前末尾节点
last
指向新节点 - 接下来有一个判断,我的理解是判断当前插入的元素是否是第一个元素(
l == null
是判断当前链表是否有节点),如果是第一个元素,则将当前插入的元素作为第一个节点,让first
指向这个节点,若不是,若不是则当前末尾节点的next
指向新增的节点
LinkedList还提供了linkFirst(E e)在头部插入元素
和linkBefore(E e, Node<E> succ)在一个非空节点之前插入元素
的方法
删除元素
LinkedList中提供了两个方法删除节点,如下源码所示
/*
* 方法一
* 删除此列表中指定位置的元素
*/
public E remove(int index) {
//检查索引是否正确
checkElementIndex(index);
//这里分为两步,第一通过索引定位到节点,第二删除节点
return unlink(node(index));
}
/**
* 方法二
* 如果指定元素存在,则从此列表中删除第一个出现的指定元素
* 如果此列表不包含指定元素,则列表保持不变
*/
public boolean remove(Object o) {
//判断删除的元素是否为null
if (o == null) {
//若是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;
}
复制代码
通过源码可以看出两个方法都是通过unlink()
删除,在方法一种有个方法要介绍下,就是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的位置,是靠近
first
还是靠近last
- 若靠近
first
则从头开始查询,否则从尾部开始查询,可以看出这样避免极端情况的发生,也更好的利用了LinkedList
双向链表的特征
unlink()方法:
/**
* 取消非零节点x的链接
*/
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;
//删除的是第一个节点,first向后移动
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//删除的是最后一个节点,last向前移
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
复制代码
- 获取到需要删除元素当前的值,一个指向它前一个节点的引用,以及一个指向它后一个节点的引用
- 判断删除的是否为第一个节点,若是则
first
向后移动,若不是则将当前节点的前一个节点next
指向当前节点的后一个节点 - 判断删除的是否为最后一个节点,若是则
last
向前移动,若不是则将当前节点的后一个节点的prev
指向当前节点的前一个节点 - 将当前节点的值置为null
size
减少并返回删除节点的值modCount
是记录链表在结构上被修改的次数
LinkedList
的基本原理了解清楚,并且我们可以自己动手实现一个LinkedList
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
public class MyLinkedList<E> implements Iterable<E> {
private int size;
private int modCount = 0;
private Node<E> first;
private Node<E> last;
public MyLinkedList() {
clear();
}
private void clear() {
size = 0;
modCount++;
first = new Node<E>(null, null, null);
last = new Node<E>(first, null, null);
first.next = last;
}
public E get(int index) {
return getNode(index).item;
}
public boolean add(E elem) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, elem, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
return true;
}
public E remove(int index) {
final Node<E> node = getNode(index);
final Node<E> prev = node.prev;
final Node<E> next = node.next;
prev.next = next;
next.prev = prev;
size--;
modCount++;
return node.item;
}
public E remove(Object o) {
E e = null;
if (o == null) {
//若是null遍历删除
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
e = remove(x);
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
e = remove(x);
}
}
}
return e;
}
private E remove(Node<E> x) {
final E elem = x.item;
final Node<E> prev = x.prev;
final Node<E> next = x.next;
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 elem;
}
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;
}
}
private Node<E> getNode(int index) {
Node<E> p;
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("out of index");
}
//此处采用二分查找算法
if (index < (size >> 1)) {
p = first;
for (int i = 0; i < index; i++)
p = p.next;
} else {
p = last;
for (int i = size - 1; i > index; i--)
p = p.prev;
}
return p;
}
@Override
public Iterator<E> iterator() {
return null;
}
@Override
public void forEach(Consumer<? super E> action) {
}
@Override
public Spliterator<E> spliterator() {
return null;
}
}
复制代码