来,一起阅读源码,通过LinkedList回顾基础( 三 )


1.复制一个指向尾节点的引用l(蓝色部分)
来,一起阅读源码,通过LinkedList回顾基础文章插图
2.将待添加元素构造为一个节点newNode , prev指向尾节点(蓝色部分)
来,一起阅读源码,通过LinkedList回顾基础文章插图
3.last指向新构造的节点(红色部分)
来,一起阅读源码,通过LinkedList回顾基础文章插图
5.将添加前最后元素的next指向新节点(绿色部分)
来,一起阅读源码,通过LinkedList回顾基础文章插图
此时 , 引用newNode和l引用依然存在 , 但是已经没有意义 。
add(int index, E element)
将元素添加到指定位置
public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));}可以看出 , 该方法首先检查指定索引是否符合规则 , 也就是在index >= 0 且 index <= size;
如果index==size , 则相当于直接在链表尾部插入 , 直接调用linkLast方法;
以上不满足 , 调用linkBefore方法 , 而linkBefore中调用了node(index) 。
node(index)
node(index)作用是返回指定索引的节点 , 这里用到了我们前面说到的一个知识 , 先判断索引在前半部分还是在后半部分 , 再决定从头还是从尾开始遍历 。
Node node(int index) {// assert isElementIndex(index);//如果索引在前半部分 , 则从头结点开始往后遍历if (index < (size >> 1)) {Node x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {//如果索引在后半部分 , 则从尾向前遍历Node x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}linkBefore
回过头来看linkBefore , 参数分别为待插入的元素 , 以及指定位置的节点
void linkBefore(E e, Node succ) {// assert succ != null;//1.复制指向目标位置的上一个节点引用final Node pred = succ.prev;//2.构造新节点 , prev指向目标位置的上一个节点 , next指向原来目标位置节点final Node newNode = new Node<>(pred, e, succ);//3.原来节点prev指向新节点succ.prev = newNode;//4.如果插在头结点位置 , 则first指向新节点if (pred == null)first = newNode;//5.非头节点 , 目标位置上一节点的next指向新节点elsepred.next = newNode;size++;modCount++;}上述过程可以看出 , 关键过程在于linkBefore方法 , 我们同样画图表示:
头结点处添加:
1.复制指向目标位置的上一个节点引用
Node pred = succ.prev;
来,一起阅读源码,通过LinkedList回顾基础文章插图
本质是指向null
2.构造新节点 , prev指向目标位置的上一个节点 , next指向原来目标位置节点
Node newNode = new Node<>(pred, e, succ);
来,一起阅读源码,通过LinkedList回顾基础文章插图
3.目标节点的prev指向待添加新节点
succ.prev = newNode;
来,一起阅读源码,通过LinkedList回顾基础文章插图
4.first指向新节点
first = newNode;
来,一起阅读源码,通过LinkedList回顾基础文章插图
中间位置添加
如图 , 假设指定添加到第三个节点 , 即index=2 , 则第二个和第三个节点之间必须有断开的过程 。
来,一起阅读源码,通过LinkedList回顾基础文章插图
【来,一起阅读源码,通过LinkedList回顾基础】1.复制指向目标位置的上一个节点引用 , 也就是第二个节点
Node pred = succ.prev;
来,一起阅读源码,通过LinkedList回顾基础文章插图
2.构造新节点 , prev指向复制的上一个节点 , next指向原来目标位置上的节点
Node newNode = new Node<>(pred, e, succ);
来,一起阅读源码,通过LinkedList回顾基础文章插图
3.目标节点的prev指向待添加新节点
succ.prev = newNode;
来,一起阅读源码,通过LinkedList回顾基础文章插图
5.目标位置上一节点的next指向新节点
pred.next = newNode;
来,一起阅读源码,通过LinkedList回顾基础文章插图
get(int index)
public E get(int index) {checkElementIndex(index);return node(index).item;}get方法是按索引获取元素 , 本质调用node(index) , 上一部分已经提到了这个方法 , 虽然双向链表在一定程度上提高了效率 , 由N减少到N/2 , 但本质上时间复杂度还是N的常数倍 , 所以轻易不要用这个方法 , 在需要随机访问的时候应当使用ArrayList , 在需要遍历访问以及增删多查找少的时候考虑LinkedList 。 之所以要有这个方法是由List接口指定 , 这个方法也是LinkedList没有实现RandomAccess接口的原因之一 。