一、创建一个 LinkedList 的对象
LinkedList list = new LinkedList();
当实例化 LinkedList 时,构造器什么也没做。
public LinkedList() {
}
二、添加一个元素
LinkedList list = new LinkedList();
... // 可能添加很多个元素了
list.add(new Object());
看一下源码
public boolean add(E e) {
// 把元素追加到链表的后面
linkLast(e);
return true;
}
分析这个 linkLast(),看看它里面做了什么
void linkLast(E e) {
// 把最后一个元素的地址赋给 l
final Node<E> l = last;
/*
根据你要添加的这个元素创建一个新的节点,此时这个元素就应该是最后一个元素,所以它的前一个节点
应该是之前的最后一个元素,也就是 l。因为它是最后一个元素,所以它后面就没有节点了,也就是 null。(Node 类在下面)
*/
final Node<E> newNode = new Node<>(l, e, null);
// 把这个新的节点作为最后一个元素
// 注意:此时这个节点还没有被加入到数组中
last = newNode;
if (l == null)
// 如果 l 为 null,也就是说之前这个链表中并没有元素(first = last = null),
// 所以这个新节点既是最后一个元素也是第一个元素
first = newNode;
else
// 把这个节点放在链表中,作为之前最后一个元素的下一个元素
l.next = newNode;
size++; // 元素个数 + 1
modCount++; // 修改次数 + 1
}
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;
}
}
三、移除一个元素
ArrayList list = new ArrayList();
... // 可能添加很多个元素了
list.add(new Object());
list.remove(int index);
快乐源码,gogogo!
public E remove(int index) {
// 检查这个元素的索引是不是有效的
checkElementIndex(index);
return unlink(node(index));
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
// 判断这个 index 索引是不是在有效的范围内
return index >= 0 && index < size;
}
接下来分析这个 unlink(),在分析它之前,我们先分析一下这个 unlink() 里面的 node()
// 返回指定索引处的节点
Node<E> node(int 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;
}
}
接下来该到 unlink() 了
E unlink(Node<E> x) {
// 将要移除元素的值存起来,作为返回值
final E element = x.item;
final Node<E> next = x.next; // 它后面的那个元素
final Node<E> prev = x.prev; // 它前面的那个元素
if (prev == null) {
// 如果要移除的元素是第一个元素,就把 next 作为第一个元素
first = next;
} else {
// 如果不是第一个元素,就把前一个元素的 next 指向它的 next
prev.next = next;
x.prev = null; // help GC
}
if (next == null) {
// 如果是最后一个元素,就把 prev 作为最后一个元素
last = prev;
} else {
// 如果不是最后一个元素,就把后一个元素的 prev 指向它的前一个元素
next.prev = prev;
x.next = null;
}
x.item = null;
size--; // 元素个数 - 1
modCount++; // 修改次数 + 1
return element;
}
痛快吧,over 了
四、插入一个元素
源码走起
public void add(int index, E element) {
// 检查这个索引是否有效,无效则抛出异常,checkPositionIndex() 代码在下面
checkPositionIndex(index);
if (index == size)
// 如果你要插入到最后,那我就直接给你追加到最后得了
// linkLast() 就是之前 add() 所调用的那个方法
linkLast(element);
else
// node(index):获取 index 索引处的节点,在 remove() 那里我们刚刚见过
linkBefore(element, node(index));
}
看一下这个 checkPositionIndex(),我都不想看了,里面逻辑和之前那个差不多的
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
// 注意:这里是 index <= size,因为可以插入在最后嘛,对吧
return index >= 0 && index <= size;
}
接下来分析 linkBefore() 这个方法,里面做的事情也很简单
void linkBefore(E e, Node<E> succ) {
// succ 就是之前 index 索引处的那个节点,别换个名字就不认识了
// 获取 succ 的前一个节点
final Node<E> pred = succ.prev;
/*
根据当前要插入的这个元素创建一个新节点,因为要插入在 succ 的前面嘛,
所以它的前一个节点就是 succ 的前一个节点,它的后一个节点就是 succ
*/
final Node<E> newNode = new Node<>(pred, e, succ);
// succ 的前一个节点自然而然的就是这个新节点呗
succ.prev = newNode;
if (pred == null)
// 如果要插入的位置是 index 为 0 的位置,也就是第一个元素,那就把这个新节点作为第一个元素
first = newNode;
else
// 如果不是 index 为 0 的位置,那 succ 前面的那个节点的后一个节点就是这个新的节点
pred.next = newNode;
size++; // 元素个数 + 1
modCount++; // 修改次数 + 1
}