本文长度还算比较长,如果你觉得过于冗长,可以你可以只看源码和图片部分。但是请注意图中的绿色箭头,它代表了操作步骤。
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;
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;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
说明:以上节点命名均为LinkedList底层名称。其中,
prev代表节点指向前面节点的指针,next代表节点指向后面节点的指针。
first代表指向头节点的指针;last代表指向尾节点的指针。
上方的两个节点代表待插入节点,它们是中的数据部分e来自集合c,由Object[] a = c.toArray()
进行转换,并在Node<E> newNode = new Node<>(pred, e, null);
中创建待插入节点。
@SuppressWarnings(“unchecked”)是一个注解但是与原理解析无关紧要,除非你想看注解。
checkPositionIndex(index); // 验证你给出的插入位置是否合法
Object[] a = c.toArray(); // 将你的集合c转为数组a
int numNew = a.length;
if (numNew == 0) // 集合c为空返回失败
return false;
Node<E> pred, succ; // 用于添加操作的两个临时的指针,它们很关键,请记住它们的名字
if (index == size) { //确定你的插入位置是否在末尾
succ = null;
pred = last;
} else {
succ = node(index); //当你的插入位置不在末尾时,node方法用于确定你指定位置的节点。
pred = succ.prev; // 将指定节点的前向指针赋值给pred,如图
}
此时,succ指向index位置处的节点。addAll(Collection<? extends E> C)是插入在指定位置的,所以最后指定位置的节点相当于向后移动。
接下来,记住指针就是地址。所以pred中存放的是前一个节点的地址。
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;
}
由于节点的创建,上图将发生变化
pred.next = newNode;
pred = newNode;
接下来循环重复
此时循环结束,来到下面
if (succ == null) {
last = pred;
} else { // 插入位置不在末尾时进入else
pred.next = succ;
succ.prev = pred;
}
pred.next = succ;
succ.prev = pred;
整理一下
进一步整理,得到结果。
size += numNew; // 插入元素后,size计数值将增大
modCount++; // 无关语句,不需要在意
return true;
指针即地址
当你看到指针时,不应当仅将其想象为箭头,而是当你在分析当前节点的指针操作时应当想象指针所存储的地址;当你在移动指针时将其想象为箭头。遵循这种顺序你便可以清晰的区别源码中各个指针出现的意义以及它们所指向的节点。
END