2024年最新从源码角度彻底搞懂LinkedList,mysql数据库笔试题

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

return true;

}

/**

  • Links e as last element.将e添加到尾部

*/

void linkLast(E e) {

//1. 暂记尾节点

final Node l = last;

//2. 构建节点 前一个节点是之前的尾节点

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

//3. 新建的节点是尾节点了

last = newNode;

//4. 判断之前链表是否为空

//为空则将新节点赋给头结点(相当于空链表插入第一个元素,头结点等于尾节点)

//非空则将之前的尾节点指向新节点

if (l == null)

first = newNode;

else

l.next = newNode;

//5. 链表长度增加

size++;

modCount++;

}

大体思路:

  1. 构建一个新的节点

  2. 将该新节点作为新的尾节点.如果是空链表插入第一个元素,那么头结点=尾节点=新节点;如果不是,那么将之前的尾节点指向新节点.

  3. 增加链表长度

小细节

boolean add(E e)添加成功返回true,添加失败返回false.我们在代码中没有看到有返回false的情况啊,直接在代码中写了个返回true,什么判断条件都没有,啊??

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

仔细想想,分配内存空间不是必须是连续的,所以只要是还能给它分配空间,就不会添加失败.当空间不够分配时(内存溢出),会抛出OutOfMemory.

2. addLast(E e)

方法作用:添加元素到末尾. 内部实现和add(E e)一样.

public void addLast(E e) {

linkLast(e);

}

3. addFirst(E e)

方法作用:添加元素到链表头部

public void addFirst(E e) {

linkFirst(e);

}

/**

  • 添加元素到链表头部

*/

private void linkFirst(E e) {

//1. 记录头结点

final Node f = first;

//2. 创建新节点 next指针指向之前的头结点

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

//3. 新建的节点就是头节点了

first = newNode;

//4. 判断之前链表是否为空

//为空则将新节点赋给尾节点(相当于空链表插入第一个元素,头结点等于尾节点)

//非空则将之前的头结点的prev指针指向新节点

if (f == null)

last = newNode;

else

f.prev = newNode;

//5. 链表长度增加

size++;

modCount++;

}

大体思路:

  1. 构建一个新的节点

  2. 将该新节点作为新的头节点.如果是空链表插入第一个元素,那么头结点=尾节点=新节点;如果不是,那么将之前的头节点的prev指针指向新节点.

  3. 增加链表长度

4. push(E e)

方法作用:添加元素到链表头部 这里的意思比拟压栈.和pop(出栈:移除链表第一个元素)相反.

内部实现是和addFirst()一样的.

public void push(E e) {

addFirst(e);

}

5. offer(),offerFirst(E e),offerLast(E e)

方法作用:添加元素到链表头部. 内部实现其实就是add(e)

public boolean offer(E e) {

return add(e);

}

public boolean offerFirst(E e) {

addFirst(e);

return true;

}

/**

  • 添加元素到末尾

*/

public boolean offerLast(E e) {

addLast(e);

return true;

}

6. add(int index, E element)

方法作用:添加元素到指定位置,可能会抛出IndexOutOfBoundsException

//添加元素到指定位置

public void add(int index, E element) {

//1. 越界检查

checkPositionIndex(index);

//2. 判断一下index大小

//如果是和list大小一样,那么就插入到最后

//否则插入到index处

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

//检查是否越界

private void checkPositionIndex(int index) {

if (!isPositionIndex(index))

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

/**

  • Returns the (non-null) Node at the specified element index.

返回指定元素索引处的(非空)节点。

*/

Node node(int index) {

// assert isElementIndex(index);

/**

  • 这里的思想非常巧妙,如果index在链表的前半部分,那么从first开始往后查找

否则,从last往前面查找

*/

//1. 如果index<size/2 ,即index在链表的前半部分

if (index < (size >> 1)) {

//2. 记录下第一个节点

Node x = first;

//3. 循环从第一个节点开始往后查,直到到达index处,返回index处的元素

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

//index在链表的后半部分

//4. 记录下最后一个节点

Node x = last;

//5. 循环从最后一个节点开始往前查,直到到达index处,返回index处的元素

for (int i = size - 1; i > index; i–)

x = x.prev;

return x;

}

}

/**

  • Links e as last element.

将e链接到list最后一个元素

*/

void linkLast(E e) {

//1. 记录最后一个元素l

final Node l = last;

//2. 构建一个新节点,数据为e,前一个是l,后一个是null

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

//3. 现在新节点是最后一个元素了,所以需要记录下来

last = newNode;

//4. 如果之前list为空,那么first=last=newNode,只有一个元素

if (l == null)

first = newNode;

else

//5. 非空的话,那么将之前的最后一个指向新的节点

l.next = newNode;

//6. 链表长度+1

size++;

modCount++;

}

/**

  • Inserts element e before non-null Node succ.

在非null节点succ之前插入元素e。

*/

void linkBefore(E e, Node succ) {

// assert succ != null;

//1. 记录succ的前一个节点

final Node pred = succ.prev;

//2. 构建一个新节点,数据是e,前一个节点是pred,下一个节点是succ

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

//3. 将新节点作为succ的前一个节点

succ.prev = newNode;

//4. 判断pred是否为空

//如果为空,那么说明succ是之前的头节点,现在新节点在succ的前面,所以新节点是头节点

if (pred == null)

first = newNode;

else

//5. succ的前一个节点不是空的话,那么直接将succ的前一个节点指向新节点就可以了

pred.next = newNode;

//6. 链表长度+1

size++;

modCount++;

}

大体思路:

  1. 首先判断一下插入的位置是在链表的最后还是在链表中间.

  2. 如果是插入到链表末尾,那么将之前的尾节点指向新节点

  3. 如果是插入到链表中间

  4. 需要先找到链表中index索引处的节点.

  5. 将新节点赋值为index处节点的前一个节点

  6. 将index处节点的前一个节点的next指针赋值为新节点

哇,这里描述起来有点困难,不知道我描述清楚没有.如果没看懂我的描述,看一下代码+再结合代码注释+画一下草图应该更清晰一些.

6. addAll(int index, Collection

//将指定集合的所有元素插入到末尾位置

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

return addAll(size, c);

}

//将指定集合的所有元素插入到index位置

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

//1. 入参合法性检查

checkPositionIndex(index);

//2. 将集合转成数组

Object[] a = c.toArray();

//3. 记录需要插入的集合元素个数

int numNew = a.length;

//4. 如果个数为0,那么插入失败,不继续执行了

if (numNew == 0)

return false;

//5. 判断一下index与size是否相等

//相等则插入到链表末尾

//不相等则插入到链表中间 index处

Node pred, succ;

if (index == size) {

succ = null;

pred = last;

} else {

//找到index索引处节点 这样就可以方便的拿到该节点的前后节点信息

succ = node(index);

//记录index索引处节点前一个节点

pred = succ.prev;

}

//6. 循环将集合中所有元素连接到pred后面

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;

}

//7. 判断succ是否为空

//为空的话,那么集合的最后一个元素就是尾节点

//非空的话,那么将succ连接到集合的最后一个元素后面

if (succ == null) {

last = pred;

} else {

pred.next = succ;

succ.prev = pred;

}

//8. 链表长度+numNew

size += numNew;

modCount++;

return true;

}

大体思路:

  1. 将需要添加的集合转成数组a

  2. 判断需要插入的位置index是否等于链表长度size,如果相等则插入到链表最后;如果不相等,则插入到链表中间,还需要找到index处节点succ,方便拿到该节点的前后节点信息.

  3. 记录index索引处节点的前一个节点pred,循环将集合中所有元素连接到pred的后面

  4. 将集合最后一个元素的next指针指向succ,将succ的prev指针指向集合的最后一个元素

六、删除元素


1. remove(),removeFirst()

方法作用: 移除链表第一个元素

/**

  • 移除链表第一个节点

*/

public E remove() {

return removeFirst();

}

/**

  • 移除链表第一个节点

*/

public E removeFirst() {

final Node f = first;

//注意:如果之前是空链表,移除是要报错的哟

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

/**

  • Unlinks non-null first node f.

  • 将第一个节点删掉

*/

private E unlinkFirst(Node f) {

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

//1. 记录第一个节点的数据值

final E element = f.item;

//2. 记录下一个节点

final Node next = f.next;

//3. 将第一个节点置空 帮助GC回收

f.item = null;

f.next = null; // help GC

//4. 记录头节点

first = next;

//5. 如果下一个节点为空,那么链表无节点了 如果不为空,将头节点的prev指针置为空

if (next == null)

last = null;

else

next.prev = null;

//6. 链表长度-1

size–;

modCount++;

//7. 返回删除的节点的数据值

return element;

}

大体思路:其实就是将第一个节点移除并置空,然后将第二个节点作为头节点.思路还是非常清晰的,主要是对细节的处理.

2. remove(int index)

方法作用:移除指定位置元素

//移除指定位置元素

public E remove(int index) {

//检查入参是否合法

checkElementIndex(index);

//node(index)找到index处的节点

return unlink(node(index));

}

//移除节点x

E unlink(Node x) {

// assert x != null;

//1. 记录该节点数据值,前一个节点prev,后一个节点next

final E element = x.item;

final Node next = x.next;

final Node prev = x.prev;

//2. 判断前一个节点是否为空

if (prev == null) {

//为空的话,那么说明之前x节点是头节点 这时x的下一个节点成为头节点

first = next;

} else {

//非空的话,将前一个节点的next指针指向x的下一个节点

prev.next = next;

//x的prev置为null

x.prev = null;

}

//3. 判断x后一个节点是否为空

if (next == null) {

//为空的话,那么说明之前x节点是尾节点,这时x的前一个节点成为尾节点

last = prev;

} else {

//为空的话,将x的下一个节点的prev指针指向prev(x的前一个节点)

next.prev = prev;

//x的next指针置空

x.next = null;

}

//4. x节点数据值置空

x.item = null;

//5. 链表长度-1

size–;

modCount++;

//6. 将x节点的数据值返回

return element;

}

大体思路:

1. 首先找到index索引处的节点(这样就可以方便的获取该节点的前后节点),记为x

2. 记录x的前(prev)后(next)节点

3. 将x的前一个节点prev节点的next指针指向next,将x节点的后一个节点的prev指针指向prev节点.

4. 将x节点置空,链表长度-1

3. remove(Object o)

方法作用:从此链表中删除第一次出现的指定元素o

public boolean remove(Object o) {

//1. 判断o是否为空

if (o == null) {

//为null 循环,找第一个数据值为null的节点

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

if (x.item == null) {

//删除该节点

unlink(x);

return true;

}

}

} else {

//非空 循环,找第一个与o的数据值相等的节点

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

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

//删除该节点

unlink(x);

return true;

}

}

}

return false;

}

大体思路:

  1. 首先判断入参是否为null

  2. 如果为null,那么循环遍历链表,从头节点开始往后查找,找到第一个节点的数据值为null的,直接删除该节点.

  3. 如果非null,那么循环遍历链表,从头节点开始往后查找,找到第一个节点的数据值为o的,直接删除该节点.

这里的循环遍历链表的代码,我觉得还是比较通用的,从头节点开始,通过不断的将x赋值为下一个元素,直到遍历到为null的地方结束,这样就完美的遍历完了链表所有节点.

4. removeFirstOccurrence(Object o)

方法作用:从此链表中删除第一次出现的指定元素o. 内部其实就是上面的remove(o);

public boolean removeFirstOccurrence(Object o) {

return remove(o);

}

5. removeLast()

方法作用:移除最后一个元素并返回

public E removeLast() {

final Node l = last;

//如果链表是空的,那么就要抛出一个错误

if (l == null)

throw new NoSuchElementException();

return unlinkLast(l);

}

/**

  • Unlinks non-null last node l.

移除链表最后一个元素

*/

private E unlinkLast(Node l) {

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

//1. 记录尾节点数据值

final E element = l.item;

//2. 找到尾节点的前一个节点prev

final Node prev = l.prev;

//3. 将尾节点置空 方便GC

l.item = null;

l.prev = null; // help GC

//4. 将last赋值为prev

last = prev;

//5. 判断prev是否为null

//为空的话,说明之前链表就只有1个节点,现在删了之后,头节点和尾节点都为null了

//非空,直接将新任尾节点的next指针指向null

if (prev == null)

first = null;

else

prev.next = null;

//6. 链表长度-1

size–;

modCount++;

//7. 返回之前尾节点数据值

return element;

}

大体思路:

  1. 判断链表是否有节点, 没有节点直接抛错误….

  2. 首先找到倒数第二个节点(可能没有哈,没有的话,说明链表只有一个节点)prev

  3. 然后将尾节点置空,prev的next指针指向null

6. removeLastOccurrence(Object o)

方法作用:从此链表中删除最后一次出现的指定元素o.

实现:其实和上面的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;

}

7. poll()

方法作用:获取第一个元素的同时删除第一个元素,当链表无节点时,不会报错. 这里的unlinkFirst()上面已分析过.

public E poll() {

final Node f = first;

return (f == null) ? null : unlinkFirst(f);

}

8. pop()

方法作用:获取第一个元素的同时删除第一个元素,当链表无节点时,会报错.

public E pop() {

return removeFirst();

}

public E removeFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

七、修改元素


1. set(int index, E element)

方法作用:设置index处节点数据值为element

public E set(int index, E element) {

//1. 入参检测

checkElementIndex(index);

//2. 找到index处节点,上面已分析该方法

Node x = node(index);

//3. 保存该节点旧值

E oldVal = x.item;

结尾

查漏补缺:Java岗 千+道面试题Java基础+全家桶+容器+反射+异常等

这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。

由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ode f = first;

return (f == null) ? null : unlinkFirst(f);

}

8. pop()

方法作用:获取第一个元素的同时删除第一个元素,当链表无节点时,会报错.

public E pop() {

return removeFirst();

}

public E removeFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

七、修改元素


1. set(int index, E element)

方法作用:设置index处节点数据值为element

public E set(int index, E element) {

//1. 入参检测

checkElementIndex(index);

//2. 找到index处节点,上面已分析该方法

Node x = node(index);

//3. 保存该节点旧值

E oldVal = x.item;

结尾

[外链图片转存中…(img-fmIWMB9M-1715237403319)]

这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。

由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值