LinkedList实现原理(JDK1

}

private boolean isPositionIndex(int index) {

return index >= 0 && index <= size;

}

如果index == size,其实就是尾部插入,所以调用了linkLast,这个刚刚尾部插入已经说过。

如果index < size,中间插入的时候,需要分两步:

  1. node(int index)方法获取到index位置的元素succ

  2. linkBefore(E e, Node succ)将需要插入的元素element连接到succ后面

node方法是一个频繁被调用的方法,LinkedList 的很多操作都依赖于该方法查找到对应的元素。根据索引 index 获取元素时,因为双向链表的支持前后遍历,所以进行了位置判断,index < (size >> 1),与中间位置比较,靠前则前序遍历,否则后序遍历。

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;

}

}

遍历逻辑很简单,循环到index上一个节点(后序则是下一个)位置,获取next(后序使用prev)返回index位置对应的节点Node对象succ。

void linkBefore(E e, Node succ) {

// assert succ != null;

final Node pred = succ.prev;

final Node newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size ;

modCount ;

}

linkBefore和linkLast几乎完全一样,除了一个是添加到 last 节点后,一个是添加到 succ 节点后。

对于中间插入,如果index为0时,其实就是头部插入,这个时候比不用调用node方法去查找元素了,所以LinkedList也提供了一个addFirst(E e)方法。

public void addFirst(E e) {

linkFirst(e);

}

private void linkFirst(E e) {

final Node f = first;

final Node newNode = new Node<>(null, e, f);

first = newNode;

if (f == null)

last = newNode;

else

f.prev = newNode;

size ;

modCount ;

}

3.3.批量插入

LinkedList提供尾部批量插入和中间批量插入,但内部实现其实都是调用的addAll(int index, Collection<? extends E> c)。

public boolean addAll(Collection<? extends E> c) {

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;

// succ是index位置元素,pred是index的前一个元素

Node 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 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;

}

addAll(int index, Collection<? extends E> c)方法初一看,好像有些复杂,但明白其原理后,就变得清晰多了。链表插入就如同接水管,先从某一个位置断开水管,然后用接口连接上需要接入的部分。这个方法里,关键的是两个Node对象pred, 和 succ,succ 是index位置元素,pred是index的前一个元素(变动)。

特殊情况 index == size 时,即尾部插入,所以succ 就是null了,而 pred则为尾部节点last。

然后就是循环赋值了,在循环中构造node节点,类似于linkLast。

最后的是衔接处理,如果尾部插入的话,那pred就是尾部节点了(循环赋值时有pred = newNode处理),所以只需要指定last = pred。而中间插入,指明 pred.next = succ、succ.prev = pred即将index位置与新的前一个元素绑定到一起。

4.查找元素

LinkedList 除了提供通用的 get,因为其属性中含有 first 和 last 节点,也提供了 getFirst 和 getLast 方法。

public E getFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return f.item;

}

public E getLast() {

final Node l = last;

if (l == null)

throw new NoSuchElementException();

return l.item;

}

对于getFirst 和 getLast,因为是成员变量,省去了查找的过程,直接返回其节点 item 即可。

public E get(int index) {

// 范围校验

checkElementIndex(index);

// node方法获取节点

return node(index).item;

}

而通过指针的获取,主要就是调用node方法找对index对应的节点,node方法前面已经讲过,不再累述。

5.修改元素

对于LinkedList集合中元素的修改,需要先查找到该元素,然后更改其Node节点数据item即可。

public E set(int index, E element) {

// 范围检查

checkElementIndex(index);

// 获取index对应位置的Node对象

Node x = node(index);

E oldVal = x.item;

x.item = element;

return oldVal;

}

6.删除元素

LinkedList提供了很多种删除元素的方法,但是内部实现逻辑基本都相同,即找到对应的Node节点,然后将指向该节点的指向替换。

6.1.根据索引移除

我们先来看看根据索引的remove(int index)方法。

public E remove(int index) {

// 范围检查

checkElementIndex(index);

// 解除节点指针连接

return unlink(node(index));

}

删除时的范围检查就不说了,node方法也不再多提,删除的主要逻辑就是unlink(Node x)方法。

E unlink(Node x) {

// assert x != null;

final E element = x.item;

// 下一节点

final Node next = x.next;

// 前一节点

final Node prev = x.prev;

// 前一节点prev存在则将prev的下一节点指向next,不存在则当前移除节点其实就是头结点,next就是新的first

if (prev == null) {

first = next;

} else {

prev.next = next;

x.prev = null;

}

// 下一节点next存在,则将next上一节点指向prev,不存在则说明当前移除的是未节点

if (next == null) {

last = prev;

} else {

next.prev = prev;

x.next = null;

}

// 触发GC工作

x.item = null;

size–;

// 操作计数器自增

modCount ;

return element;

}

整个unlink方法就是个标准的双向链表删除操作,三个节点prev,x,next,删除x 其实就是将 prev指向next,并next指向prev,只是其中多了一些特殊的判断。

看了按索引删除的remove,再来看另外两个特例removeFirst 和 removeLast。

public E removeFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

private E unlinkFirst(Node f) {

// assert f == first && f != null;

final E element = f.item;

final Node next = f.next;

f.item = null;

f.next = null; // help GC

first = next;

if (next == null)

last = null;

else

next.prev = null;

size–;

// 操作计数器自增

modCount ;

return element;

}

unlinkFirst就是个简化版的unlink方法,因为只用处理头结点,下一个节点next存在就将next作为新的first。

同理removeLast也是类似,这里各位看官自行进行体会。

public E removeLast() {

final Node l = last;

if (l == null)

throw new NoSuchElementException();

return unlinkLast(l);

}

private E unlinkLast(Node l) {

// assert l == last && l != null;

final E element = l.item;

final Node prev = l.prev;

l.item = null;

l.prev = null; // help GC

last = prev;

if (prev == null)

first = null;

else

prev.next = null;

size–;

modCount ;

return element;

}

而LinkedList还有个无参的remove,这个是Deque接口定义的,实现也就是调用的removeFirst。

public E remove() {

return removeFirst();

}

6.2.根据元素移除

根据元素移除其实和根据索引移除没有太大差别,只不过找到对应节点的方式发生了变化。

public boolean remove(Object o) {

// 判断元素是否为null,因为LinkedList支持添加null

if (o == null) {

for (Node x = first; x != null; x = x.next) {

if (x.item == null) {

unlink(x);

return true;

}

}

} else {

for (Node x = first; x != null; x = x.next) {

if (o.equals(x.item)) {

unlink(x);

return true;

}

}

}

return false;

}

可以看到,无论元素是否为null,都是先找到该节点,然后调用了unlink方法。

因为支持双向遍历的特性,LinkedList很人性的提供了前序删除和后序删除的方法,即removeFirstOccurrence与removeLastOccurrence。

public boolean removeFirstOccurrence(Object o) {

return remove(o);

}

public boolean removeLastOccurrence(Object o) {

if (o == null) {

// 后序遍历

for (Node x = last; x != null; x = x.prev) {

if (x.item == null) {

unlink(x);

return true;

}

}

} else {

// 后序遍历

for (Node x = last; x != null; x = x.prev) {

if (o.equals(x.item)) {

unlink(x);

return true;

}

}

}

return false;

}

这两个方法没什么玄幻的,removeLastOccurrence只是反序遍历了集合。

6.3.批量删除

在LinkedList类中,并没有removeAll方法,因为他未对其进行重写,而是使用了父类AbstractCollection的

public boolean removeAll(Collection<?> c) {

Objects.requireNonNull©;

boolean modified = false;

// 使用迭代器

Iterator<?> it = iterator();

while (it.hasNext()) {

if (c.contains(it.next())) {

it.remove();

modified = true;

}

}

return modified;

}

removeAll的实现原理其实就是迭代删除,迭代器的获取方法iterator()在AbstractCollection类中只是个抽象方法,AbstractList类有其实现,但AbstractSequentialList类中覆写该方法。

public Iterator iterator() {

return listIterator();

}

iterator方法会调用listIterator(),这个方法实现在AbstractList类中,他调用了listIterator(int index)方法,但LinkedList重写了该方法,所以兜兜转转最终还是回到了LinkedList中。

public ListIterator listIterator(int index) {

checkPositionIndex(index);

return new ListItr(index);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
stractList类中,他调用了listIterator(int index)方法,但LinkedList重写了该方法,所以兜兜转转最终还是回到了LinkedList中。

public ListIterator listIterator(int index) {

checkPositionIndex(index);

return new ListItr(index);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-yyL8flIZ-1715797115358)]

[外链图片转存中…(img-VLhGP91k-1715797115358)]

[外链图片转存中…(img-b0PZVnGM-1715797115359)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值