链表是数据元素的线性集合,元素的线性顺序并不对应于内存的物理地址顺序,每个元素指向下一个元素,这样构成了线性序列。
链表有单向链表、双向链表两种类型。
一开始我以为ArrayList是单向链表,虽然说ArrayList也实现了List接口,后面仔细查了查发现并不是,单向链表是一个节点只有后向指针的链表,数据结构如图。

代码上就是LinkedList中的Node节点没有pre指针,只有next指针,另外list的属性也是只有head和size,没有tail只想尾部的指针。
双向链表中的Node节点既有next指针也有pre指针,另外list的属性也是有size、head和tail,数据结构如下图。

平常我们多使用LinkedList双向链表,这里手写一个双向链表来举例。
因为链表所有的核心操作都是要对节点的使用,所以节点类是链表的关键。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E item, Node<E> next) {
this.item = item;
this.prev = prev;
this.next = next;
}
} void linkFirst(E e) {
// 保存当前头部节点
Node<E> first = head;
// 创建新节点,插入为新的头部节点
Node<E> newNode = new Node<E>(null, e, first);
// 更新头部节点为新节点
head = newNode;
// 如果链表之前为空(即原头节点为null),则新节点也是尾部节点
if (first == null) {
tail = newNode;
} else {
// 否则,更新原头部节点的前驱指针,指向新节点
first.prev = newNode;
}
// 增加链表大小计数
size++;
}
void linkLast(E e) {
// 保存当前尾部节点
Node<E> last = tail;
// 创建新节点,它的前驱是当前的尾部节点,后继为null
Node<E> newNode = new Node<>(last, e, null);
// 更新尾节点为新节点
tail = newNode;
// 如果尾节点为空,说明列表之前是空的,新节点也应该是头部节点
if (last == null) {
head = newNode;
} else {
// 否则,更新原尾节点的后继引用为新节点
last.next = newNode;
}
size++;
}
void unlink(Node<E> x) {
Node<E> next = x.next;
Node<E> prev = x.prev;
if (prev == null) {
head = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
tail = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
}删除一个元素必须要先for循环找到此元素,然后对其进行拆链。循环的过程是一个 O(n) 的操作,删除(拆链)的过程是一个 O(1) 的操作。
@Override
public boolean remove(E e) {
if (e == null) {
for (Node<E> x = head; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = head; x != null; x = x.next) {
if (x.item.equals(e)) {
unlink(x);
return true;
}
}
}
return false;
}public static void main(String[] args) {
List<String> list = new LinkedList<>();
// 添加元素
list.add("a");
list.addFirst("b");
list.addLast("c");
// 打印列表
list.printLinkedList();
// 头插元素
list.addFirst("d");
// 删除元素
list.remove("b");
// 打印列表
list.printLinkedList();
}测试结果

链表是一个由一个个节点构成的数据结构,每一个节点都有前驱指针和后向指针,所有的节点构成了线性序列,但是线性顺序并不对应于内存上物理地址的顺序。
双向链表
插入是O1 删除是O1,但是为了找到要删除的节点,需要for循环遍历,时间复杂度为On 获取元素是On
插入删除操作较多,查询元素操作较低的场景下更合适。查询操作更推荐用ArrayList。