正文
上一篇博客看了下 ArrayList, 这次来看一下 LinkedList,集合中,LinkedList 是一个比较重要的常用集合,LinkedList 实现了 List 接口,也实现了 Deque 接口,所以 LinkedList 既可以作为正常的 list 类使用, 也可以作为双向队列使用,同时还能作为栈使用。
LinkedList 内部实现是一个双向的链表,所以在初始化 LinkedList 的时候,不需要指定容量,因此其构造器也很简单。
// todo 双向链表
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;
}
}
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
// size 初始值是 0, 表示从 size 位置开始赋值进去
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 检查下标
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
// 如果入参集合是空的,就返回
if (numNew == 0)
return false;
Node<E> pred, succ;
// todo 寻找 index 位置和 index 前驱节点
if (index == size) {
succ = null;
pred = last;
} 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
pred.next = newNode;
pred = newNode;
}
// succ == null 说明就是从最后一个元素开始赋值,赋值完毕,将 last 设置为当前的 pred
if (succ == null) {
last = pred;
} else {
// 否则说明是在链表中间开始赋值,赋值完毕后, last 不需要更改,但是赋值位置的顺序需要变更
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
可以看到,LinkedList 中的 Node 节点就是一个双向的链表, 构造器一共有两种,一种是空构造,一种是复制的构造,将其他集合类型的数据复制到新的 LinkedList 中,通过 addAll 方法,addAll 方法也比较简单。
看一下内部私有的一些操作方法
linkFirst 方法
/**
* Links e as first element.
* todo 链到链头上
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
// 链表头设置为当前 node
first = newNode;
// todo 如果当前是空的链表,就将链尾设置为当前的 node,否则,就将之前的头结点的前驱设置为当前节点
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
linkLast 方法
/**
* Links e as last element.
* todo 链到链尾上
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
// todo 将 last 指针指向当前节点
last = newNode;
// todo 如果之前的 last 是空的,就将头结点设置当前节点,否则将之前的尾节点的后继设置为当前节点
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
linkBefore 方法
/**
* Inserts element e before non-null Node succ.
* todo 链接到 succ 的前驱上
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
// todo 将 succ 的前驱设置为新节点前驱, succ 设置为新节点后继
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
unlinkFirst 方法
/**
* Unlinks non-null first node f.
* todo 删除链头
*/
private E unlinkFirst(Node<E> f) {
// todo f 是头结点
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
// todo 如果头结点的后继是 null,说明已经是最后一个元素,那么就将 last 设置为 null,否则就将 next 的前驱设置为 null,成为新的头结点
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
unlinkLast 方法
/**
* Unlinks non-null last node l.
* todo 删除链尾
*/
private E unlinkLast(Node<E> l) {
// todo l 是尾节点并且 l 不等于 null
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
// todo 如果尾节点的前驱是 null,说明尾节点 == 头结点,那么就将头结点指针也设置为 null,否则就将前驱节点的后继设置为 null
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
unlink 方法
/**
* Unlinks non-null node x.
* todo 删除指定节点,这里的两个操作分别对前驱节点和后继节点操作,单独处理前驱节点和后继节点以及头尾指针的关系
*/
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;
// todo 如果前驱节点 == null,说明是头结点,就将头结点设置为 next,否则就将 x 节点的前驱节点的后继节点设置为 x 的后继节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// todo 如果 next 节点是空的,就说明 next 节点是原先的尾节点,就需要将尾节点指针 last 重新赋值,否则就将 next 节点的前驱设置为 x 节点的前驱
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// todo 将 x 节点的内容设置为 null,方便 gc
x.item = null;
size--;
modCount++;
return element;
}
node 方法
/**
* Returns the (non-null) Node at the specified element index.
* todo 寻找指定下标节点
*/
Node<E> node(int index) {
// assert isElementIndex(index);
// todo 如果节点小于 size 的一半,从头开始寻找,否则从尾部开始寻找
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;
}
}
LinkedList 的公用方法大部分都是基于上面的这些内部方法实现的,上面的方法包含了几乎所有对 LinkedList 的操作,有兴趣的可以看看公用的方法,实际上就是调用上面的这些方法实现的,常用的 get/set 类的方法就不展开说了, 下面说一下除了上面以及常用的 get/set 方法以为的方法
toArray 方法
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
public <T> T[] toArray(T[] a) {
// todo 如果传入的数组容量不够,会通过反射新建一个
if (a.length < size)
a = (T[]) java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
特别小心第二个 toArray 方法,是可以带外部数组的,如果带进来的数组容量够的话,就会使用,不够的情况会新建一个,所以一定要使用返回值,不要忽略返回值
indexOf 方法 (lastIndexOf 方法只是遍历的顺序不同而已)
public int indexOf(Object o) {
int index = 0;
//todo 遍历找到第一个符合条件的元素,从头开始,找不到返回 -1
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;
}
本文首次发布于 fyypumpkin Blog, 作者 @fyypumpkin ,转载请保留原文链接.