LinkedList实现原理(JDK1,java语言程序设计基础篇答案百度云

void linkLast(E e) {

final Node l = last;

// 构造新的节点,上一节点指向原来的last

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

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size ;

// 操作数自增

modCount ;

}

尾部添加,就是构造新的节点newNode,并将改节点的上一节点prev指向原来的last,同时改节点为新的last节点。l == null判断是否当前是第一次添加,如果 l 为 null,则newNode同时也是头结点,当前集合中仅有newNode一个元素,不为null时,因为双向链表,所有 l 的下一个节点next指向了newNode。

最后就是大小size自增与操作计数modCount的自增,尾部添加元素就完成了。

尾部操作除了add,还有个addLast(E e) 方法,两者除了返回值不一样,没有任何差别了,都是调用的linkLast方法。

public void addLast(E e) {

linkLast(e);

}

3.2.中间添加

public void add(int index, E element) {

// 范围检查

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

对于中间添加,需要首先进行范围检查,即保证插入位置index在[0, size]之间,否则抛出数组越界异常IndexOutOfBoundsException,呃……数组越界……

private void checkPositionIndex(int index) {

if (!isPositionIndex(index))

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

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

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

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

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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinkedList(链表)是一种常见的数据结构,它由一系列节点组成,每个节点都包含一个数据元素和一个指向下一个节点的指针。 LinkedList实现原理可以简单概括为以下几点: 1. 节点定义:链表的节点通常会包含两个成员,一个是存储数据的变量,另一个是指向下一个节点的指针。节点定义类似于以下形式: ``` class Node { int data; Node next; } ``` 2. 头节点:链表的头节点是链表的第一个节点,通过头节点可以遍历整个链表。头节点通常被定义为链表的成员变量。 3. 添加元素:在链表中添加元素通常有两种方式,分别是在链表的开头添加元素和在链表的末尾添加元素。 - 在链表开头添加元素:将新节点的 next 指针指向原来的头节点,然后将新节点设为新的头节点。 - 在链表末尾添加元素:遍历整个链表,直到找到最后一个节点,然后将新节点的地址赋给最后一个节点的 next 指针。 4. 删除元素:在链表中删除元素通常也有两种方式,分别是删除头节点和删除指定位置的节点。 - 删除头节点:将头节点的下一个节点设为新的头节点。 - 删除指定位置的节点:遍历链表,找到要删除节点的前一个节点,然后将前一个节点的 next 指针指向要删除节点的下一个节点。 5. 遍历链表:通过头节点可以遍历整个链表,从头节点开始,依次访问每个节点,并根据需求进行相应的操作。 LinkedList实现原理基于节点之间通过指针相互连接,可以动态地添加、删除节点,所以在插入和删除操作上相对于数组会更加高效。但是链表的缺点是访问某个特定位置的元素需要从头节点开始遍历,而数组可以通过索引直接访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值