Debug LinkedList
时间:2022-07-22
本文章向大家介绍Debug LinkedList,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Debug LinkedList源码
前置知识
- LinkedList基于链表,LinkedList的Node节点定义
- 成员变量 //链表中元素的数量 transient int size = 0; /** * 链表的头节点:用于遍历 */ transient Node<E> first; /** * 链表的尾节点:用于添加元素 */ transient Node<E> last;
2.1 Debug 分析第一个元素是如何进入链表的
编写测试代码,打上断点:
进入方法内部:
- add方法默认添加到链表的尾部
- 该方法等同于addLast(区别就是add方法需要返回一个true,而addLast没有任何返回值)
进入linkLast方法内部:
/**
* 当前方法执行完后,若添加的节点为第一个节点,链表的last和first都指向新节点
*/
void linkLast(E e) {
//获取到链表的最后一个元素
final Node<E> l = last;
//创建一个节点,前一个节点为当前链表的last,后一个节点为空
final Node<E> newNode = new Node<>(l, e, null);
//修改last为新添加的节点
last = newNode;
//若链表为空,first节点为新添加的节点,否则添加前最后一个节点的next指向新节点
if (l == null)
first = newNode;
else
l.next = newNode;
//链表长度加一
size++;
//链表修改次数加1
modCount++;
}
//=================================================================
Node带三个参数的构造函数:
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
2.2 Debug 分析如何通过下标插入指定位置add(index,e)
编写测试用例:
进入方法内部:
public void add(int index, E element) {
//检查下标是否合法:大于等于0小于等于size【return index >= 0 && index <= size;】
checkPositionIndex(index);
//若等于size,相当于在链表尾部添加节点
if (index == size)
linkLast(element);
else
//linkBefore中的Before指的是传入索引元素前
linkBefore(element, node(index));
}
进入node(index)方法:返回索引为index的元素
Node<E> node(int index) {
// assert isElementIndex(index);
//查找元素的思路是遍历一半,先折半分区,然后若在小于折半的区域就从first开始往后遍历,反之从last往前遍历
//右移一位相当于除以2
if (index < (size >> 1)) {
//获取到first节点
Node<E> x = first;
//从first遍历到index的位置
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//获取到next的节点
Node<E> x = last;
//从last往前遍历到index的位置
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
回到linkBefore方法:
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取index节点的上一个节点
final Node<E> pred = succ.prev;
//创建新节点,下一个节点指向index节点,上一个节点指向index节点的上一个节点
final Node<E> newNode = new Node<>(pred, e, succ);
//index节点的上一个节点指向变为指向新节点
succ.prev = newNode;
//**若index节点的头节点为空,相当于从头部插入节点,直接将新节点赋给first节点
if (pred == null)
first = newNode;
else
//index的上一个节点的下一个节点指向改为新节点
pred.next = newNode;
//节点长度+1
size++;
//链表修改次数+1
modCount++;
}
2.3 Debug 分析如何通过下标获取指定元素
编写测试代码,打上断点:
进入get方法内部:
public E get(int index) {
//检查下标范围
checkElementIndex(index);
//遍历一半,返回节点的值
return node(index).item;
}
LinkedList支持的查询除了通过下标获取外,还支持getLast和getFirst
get(0)和getFirst时间复杂度有区别吗?
理论上来说,getFirst和getLast都是直接获取到节点,时间复杂度为O(1),而通过get(index)方法查询节点,都会折半后遍历一半的元素,时间复杂度为O(n)。但实际情况下,for循环遍历的第一个元素就100%是头节点或尾节点,不会进入之后的循环,实际运行的也是O(1)。
2.4 Debug 分析如何通过下标删除元素
打上断点:
进入方法内部:
public E remove(int index) {
//检查下标范围
checkElementIndex(index);
//node(index)获取需要删除的节点,折半遍历
return unlink(node(index));
}
进入unlink方法内部:
删除中间元素的过程图:
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
//若删除的节点为头节点,将当前节点的下一个节点赋值给first节点
first = next;
} else {
//若删除的节点为中间节点,将当前节点的上一个节点的下一个节点指向变为当前节点的下一个节点(step1)
prev.next = next;
//当前节点的上一个节点指向置为null(step2)
x.prev = null;
}
if (next == null) {
//若删除的节点为尾节点,将当前节点的上一个节点赋给last
last = prev;
} else {
//若删除的节点为中间节点,将当前节点的下一个节点的上一个节点指向变为当前节点的上一个节点(step3)
next.prev = prev;
//当前节点的下一个节点指向置为null(step4)
x.next = null;
}
//节点元素内容置为空
x.item = null;
//链表长度-1
size--;
//链表修改次数+1
modCount++;
//返回删除节点内容
return element;
}
2.5 Debug 分析如何通过对象删除节点(内容)
public boolean remove(Object o) {
//若节点为null,从first往下遍历(说明LinkedList是允许为空值的,并且允许多个)
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
}
//若节点不为空,遍历链表后删除节点
else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
- 用事实说话,成熟的ORM性能不是瓶颈,灵活性不是问题:EF5.0、PDF.NET5.0、Dapper原理分析与测试手记
- iPhone的Wi-Fi芯片漏洞利用POC公布,赶紧更新系统吧
- No.003 Longest Substring Without Repeating Characters
- 【Spark研究】极简 Spark 入门笔记——安装和第一个回归程序
- 通常Java开发人员如何进行数据排序?
- 消息服务框架使用案例之--大文件上传(断点续传)功能
- Java中三种Set类型用法、性能大比拼
- Android基础总结(5)——数据存储,持久化技术
- 如何突破Windows环境限制打开“命令提示符”
- 【Spark研究】Spark之工作原理
- Java中泛型使用的必要性
- “一切都是消息”--MSF(消息服务框架)之【发布-订阅】模式
- Android基础总结(4)——广播接收器
- “一切都是消息”--MSF(消息服务框架)之【请求-响应】模式(点对点)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Flutter基础widgets教程-Row篇
- padans 关于数据处理的杂谈 -- 时序数
- python pandas DataFrame 关于重复索引取值的一些坑
- selenium webdriver 如何添加cookie
- Flutter基础widgets教程-Scaffold篇
- python selenium 微信公众号历史文章随手一点就返回首页?郁闷之下只好将他们都下载下来。
- Flutter基础widgets教程-SimpleDialog篇
- python selenium 关于将网页打包为静态网页(mhtml)下载。
- Redis:哨兵
- Python中设置指定窗口为前台活动窗口(最顶层窗口)win32gui
- 关于pandas.eval使用的一些问题。
- Flutter基础widgets教程-Slider篇
- 带你认识Pytest(三)
- Additive Powers-of-Two (APoT) Quantization:硬件友好的非均匀量化方法
- Flutter基础widgets教程-SnackBar篇