数据结构(二):单向链表、双向链表
数据结构(二)
- 一、什么是链表
- 1.数组的缺点
- 2.链表的优点
- 3.链表的缺点
- 4.链表和数组的区别
- 二、封装单向链表
- 1. append方法:向尾部插入节点
- 2. toString方法:链表元素转字符串
- 3. insert方法:在任意位置插入数据
- 4.get获取某个位置的元素
- 5.indexOf根据元素值返回元素位置
- 6.update更新某个位置的元素
- 7.removeAt删除某个位置的节点
- 8.remove删除指定data的元素
- 9.判断是否为空、输出长度
- 三、封装双向链表
- 1.什么是双向链表
- 2.封装双向链表:结构搭建
- 3.append向尾部插入元素
- (1)链表为空,添加的是第一个节点
- (2)添加的不是第一个节点,那么改变相关指向
- 4.toString链表数据转换为字符串
- 5.insert向任意位置插入节点
- 6.get获取某个位置的元素值
- 7.indexOf根据某个元素值获取该节点位置
- 8.update更新某个元素
- 9.removeAt删除某个位置的节点
- 10.remove删除某个元素
- 11.其他方法
一、什么是链表
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有的语言称为指针或连接)组成。类似于火车头,一节车厢载着乘客(数据),通过节点连接另一节车厢。


- head属性指向链表的第一个节点;
- 链表中的最后一个节点指向null;
- 当链表中一个节点也没有的时候,head直接指向null;
1.数组的缺点
1、数组的创建通常需要申请一段连续的内存空间(一整块内存),并且大小是固定的。所以当原数组不能满足容量需求时,需要扩容(一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)。
2、在数组的开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
2.链表的优点
1、链表中的元素在内存中不必是连续的空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
2、链表不必在创建时就确定大小,并且大小可以无限地延伸下去。
3、链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多。
3.链表的缺点
1、链表访问任何一个位置的元素时,都需要从头开始访问,需要顺着指针一个一个找(无法跳过第一个元素访问任何一个元素)。
2、无法通过下标值直接访问元素,需要从头开始一个个数组内和指针访问,直到找到对应的元素,这也是和数组最明显的区别。
3、虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
4.链表和数组的区别
二、封装单向链表
首先定义一个节点类,用来存储新声明的节点
//定义一个节点类,用来声明新的节点
class Node {constructor(data) {this.data = data; //数据this.next = null; //指针}
}
开始封装,声明两个属性,分别是头部指针和长度。
1. append方法:向尾部插入节点
1、先声明一个新的节点,判断链表是否有数据。如果没有数据,直接head指向新节点。
2、如果有数据,那么就要声明一个变量current来存储当前的指针。这个指针第一次肯定指向第一个元素(看代码理解),然后循环看一下current.next是否有指向,如果有,就改变current为current.next,直到current.next为空,说明已经到链表尾部了。
3、最后,将current指针指向新节点,搞定。

//封装一个单向链表
class LinkedList {constructor() {this.head = null;this.length = 0;}//将元素插入链表尾部的方法append(data) {//1.声明一个新的节点let newNode = new Node(data);if(this.length == 0) {//2.1如果链表为空,那么head直接指向新节点this.head = newNode;}else {//2.2如果链表不为空,那么循环遍历,直到指针指向最后一个节点let current = this.head; //这里每次都是指向第一个元素while(current.next) {current = current.next;}current.next = newNode;}//3.添加完之后length+1this.length++;}
}
2. toString方法:链表元素转字符串
//转字符串的方法
toString() {let result = '';let current = this.head;while (current) {result += current.data + ' '; //如果有就拼接current = current.next; //指针指向后面的元素}return result;
}
顺便测试一下append方法好不好使
let list = new LinkedList();
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
3. insert方法:在任意位置插入数据
情况1:在position为0的位置插入数据,那么这样的话就要
1、先让插入的新元素指向原来的第一个元素
2、然后再让head指向插入的新元素。

情况2:往其他位置插入
1、首先应该拿到该位置的那个元素
2、让新节点指向该元素
3、让该位置前边的元素指向新节点

代码(仔细看注释):
//任意位置插入元素
insert(position, data) {//1.对position进行越界判断//position==this.length说明是往最后插,如果比它大就不行了if(position < 0 || position > this.length) return false;//2.生成新节点let newNode = new Node(data);//3.对position的不同情况进行判断//3.1如果插入的位置是第一个(position=0)if(position == 0) {newNode.next = this.head; //先保存原来的第一个元素this.head = newNode; //再把新节点给head//注意:上面这两步顺序不能反,不然就找不到原来的第一个元素了}else {//如果是往其他位置插入元素let current = this.head; //拿到第一个元素let previous = null; //声明变量存储position前边的元素//循环查找,直到找到这个位置的元素for(let i = 0; i < position; i++) {previous = current; //保存上一个位置current = current.next; //保存当前位置}//循环结束后current已经是position位置的元素newNode.next = current; //让新节点指向当前位置节点(挤到后边去)previous.next = newNode; //让前一个节点指向新节点}//4.长度加1this.length++;return true;
}
测试代码:
let list = new LinkedList();
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
list.insert(2,'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
4.get获取某个位置的元素
传入位置返回当前位置元素。
主要的思路就是从head找,通过循环依次改变指针的指向,直到指针指向当前位置,那么就把当前位置返回,(如果用户输入0,那么直接返回this.head)
//获取某个位置的元素
get(position) {//1.越界判断,如果获取最后一个位置后面,那么是没有滴,所以是>=lengthif (position < 0 || position >= this.length) return false;//2.从头往后找,直到找到该位置元素let current = this.head;for (let i = 0; i < position; i++) {current = current.next;}return current;
}
测试代码:
let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个位置元素
console.log(list.get(2)); //Node {data: 'ht', next: Node}
5.indexOf根据元素值返回元素位置
主要的思路是从head依次往后查找(需要定义指针current),如果当前指针不为空,就拿传入的元素值和当前current指针的元素值比较,如果相等说明是我们要找的,直接return,不相等就要依次往后指,并且index要+1
indexOf(data) {let current = this.head;let index = 0; //定义一个索引//从头开始一个一个对比,如果相等了就返回index的值while(current) {if(current.data == data) {return index;}else {//当前节点不是我要找到节点,那么就往后指current = current.next;index++;}}//如果所有都对比完了都没找到,说明没有,就返回-1return -1;}
测试代码:
let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个位置元素
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//4.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
6.update更新某个位置的元素
主要思路:
1、通过声明current指针,拿到这个位置的元素
2、改变该元素内部的data的值,不需要改变前后指针(因为对象地址不会变)
//更新某个位置的元素值
update(position, data) {//1.越界判断,positon==length是刚好在最后一个元素的后面那个空位if(position < 0 || position >= this.length ) return false;//2.拿到这个位置节点let current = this.head;let index = 0;while(index++ < postion) {current = current.next;}//3.修改当前节点的元素值current.data = data;return current;
}
测试更新:
let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');
console.log(list.toString()); //DantinZhang zzy 修改元素 元素
7.removeAt删除某个位置的节点
1、如果删除的是第一个位置,那么head直接指向第二个节点,其他不用动(被删除的第一个节点由于没有指针指向它,会被垃圾回收机制回收)

2、如果删除的是其他的位置,就要让被删除节点的上一个节点指向被删除节点的下一个节点

代码如下:
//删除指定位置的节点
removeAt(position) {//1.越界判断if(position < 0 || position >= this.length) return false;let current = this.head;//2.1如果删除的是第一个节点,前边没东西,只有headif(position == 0) {this.head = current.next;}else {//2.2如果删除的是其他位置的节点let index = 0;let previous = null;//2.2.1先找到前边的节点,删除节点,后边的节点while(index++ < position) {previous = current;current = current.next;}//2.2.2将前边的节点指向后边的节点previous.next = current.next;}//3.长度-1this.length--;//4.返回被删除的元素return current;
}
测试代码:
let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');
console.log(list.toString()); //DantinZhang zzy 修改元素 元素
//6.测试删除指定位置元素
list.removeAt(2);
console.log(list.toString()); //DantinZhang zzy 元素
8.remove删除指定data的元素
这里的思路就是先找到data对应的索引,然后删除,直接调用就行了
//删除指定data所在的节点
remove(data) {//1.先找到data所在的位置let index = this.indexOf(data);//2.删除该位置的元素return this.removeAt(index);
}
9.判断是否为空、输出长度
//判断链表是否为空
isEmpty() {return this.length == 0;
}//判断链表的长度
size() {return this.length;
}
全部测试代码:
let list = new LinkedList();
//1.测试尾部插入
list.append('DantinZhang');
list.append('zzy');
list.append('元素');
console.log(list.toString()); //DantinZhang zzy 元素
//2.测试任意位置插入
list.insert(2, 'ht');
console.log(list.toString()); //DantinZhang zzy ht 元素
//3.测试获取某个元素索引
console.log(list.indexOf('zzy')); //1
//4.测试获取某个位置元素
console.log(list.get(2)); //Node {data: 'ht', next: Node}
//5.测试修改元素
list.update(2, '修改元素');
console.log(list.toString()); //DantinZhang zzy 修改元素 元素
//6.测试删除指定位置元素
list.removeAt(2);
console.log(list.toString()); //DantinZhang zzy 元素
//7.测试删除指定data的元素
list.remove('元素');
console.log(list.toString()); //DantinZhang zzy
//8.测试是否为空和输出长度
console.log(list.isEmpty()); //false
console.log(list.size()); //2
三、封装双向链表
1.什么是双向链表
双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。
双向链表的缺点:
1、每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;
2、 相对于单向链表,所占内存空间更大一些;
3、但是,相对于双向链表的便利性而言,这些缺点微不足道。
也就是说,双向链表就是用空间换时间

- 双向链表不仅有head指针指向第一个节点,而且有tail指针指向最后一个节点;
- 每一个节点由三部分组成:item储存数据、prev指向前一个节点、next指向后一个节点点;
- 双向链表的第一个节点的prev指向null;
- 双向链表的最后一个节点的next指向null;
2.封装双向链表:结构搭建
class Node {constructor(data) {this.pre = null;this.data = data;this.next = null;}
}class DoublyLinedList {constructor() {this.head = null;this.tail = null;this.length = 0;}
}
3.append向尾部插入元素
双向链表向尾部插入元素和单向不一样,这里不需要再遍历元素,直接拿到tail操作就可以了。在插入的时候有两种情况:1、链表为空时 2、链表不为空时
(1)链表为空,添加的是第一个节点
我们只需要让head和tail都指向这个节点就可以了

(2)添加的不是第一个节点,那么改变相关指向
改变指向分为两步,首先改变下图1、2指向,此时tail指向的是原来的最后一个节点。
newNode.pre = this.tail; //先让新节点pre指向之前的尾部节点
this.tail.next = newNode; //之前尾部节点next指向新节点

第二步,改变指向3,让tail指向新插入的最后一个节点。
this.tail = newNode; //tail指向新节点

//向尾部插入节点
append(data) {//1.先生成一个节点const newNode = new Node(data);//2.添加的是第一个节点,只需要让head和tail都指向新节点if(this.length == 0) {this.head = newNode;this.tail = newNode;} else {//3.添加的不是第一个节点,直接找到尾部(不用遍历)newNode.pre = this.tail; //先让新节点pre指向之前的尾部节点this.tail.next = newNode; //之前尾部节点next指向新节点this.tail = newNode; //tail指向新节点console.log(newNode.next); //最后一个指向null}//4.长度加一this.length++;
}
测试代码:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
console.log(list);
4.toString链表数据转换为字符串
这里有两种转字符串的方法,分别是顺序和逆序。
主要原理都一样,就是定义current变量记录当前指向的节点。首先让current指向第一个(最后一个)节点,然后通过 current = current.next 依次向后(向前)遍历。在while循环(或for循环)中以(current)作为条件遍历链表,只要current != null就一直遍历,由此可获取链表所有节点的数据。

代码:
//1.链表数据转换为字符串的两种遍历(顺序、逆序)
toString() {return this.forwardString();
}//2.顺序遍历转字符串
forwardString() {let result = '';let current = this.head;while (current) {result += current.data + ' ';current = current.next;}return result;
}//3.逆序遍历转字符串
backwardString() {let result = '';let current = this.tail;for (let i = 0; i < this.length; i++) {result += current.data + ' ';current = current.pre;}return result;
}
测试代码:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
console.log(list.toString()); //DantinZhang 努力的但丁 zzy
console.log(list.forwardString()); //DantinZhang 努力的但丁 zzy
console.log(list.backwardString()); //zzy 努力的但丁 DantinZhang
5.insert向任意位置插入节点
这里代码看着比较复杂,不过实现的思路不难。
具体可以去看这个人的博客,写的真的是非常详细:Javascript实现双向链表
//向任意位置插入节点
insert(position, data) {let newNode = new Node(data);//1.越界判断,其中length位置是最后一个节点的后面那个空位if (position < 0 || position > this.length) return false;//2.1如果链表为空if (this.length == 0) {this.head = newNode;this.tail = newNode;} else {//2.2如果链表不为空//2.2.1如果位置是1if (position == 0) {// this.head此时指的是第一个节点this.head.pre = newNode;newNode.next = this.head;this.head = newNode;}//2.2.2.如果在最后一个节点的后面插入else if (position == this.length) {// this.tail此时指的是最后一个节点newNode.pre = this.tail;this.tail.next = newNode;this.tail = newNode;}//2.2.3.如果在其他位置插入else {//先找到这个位置let index = 0;let current = this.head;let previous = null;while (index++ < position) {previous = current;current = current.next;}//插入新节点,到它们俩中间儿previous.next = newNode;newNode.pre = previous;newNode.next = current;current.pre = newNode;}}//3.长度别忘了+1this.length++;return true;
}
测试代码:
//测试代码//1.创建双向链表let list = new DoubleLinklist()//2.测试insert方法list.insert(0, '插入链表的第一个元素')list.insert(0, '在链表首部插入元素')list.insert(1, '在链表中间插入元素')list.insert(3, '在链表尾部插入元素')console.log(list);alert(list)

6.get获取某个位置的元素值
整体思路比较简单,为了提高效率,可以使用二分查找。判断位置是在前半部分还是在后半部分,如果在前半部分,就从head开始找;如果在后半部分,就从tail开始找。
//获取某个位置的元素值
get(position) {//1.越界判断if (position < 0 || position >= this.length) return false;//2.1如果该元素在前半部分,从head开始找if (position < this.length / 2) {let current = this.head;for(let i = 0; i < position; i++) {current = current.next;}return current.data;} else {//2.2如果该元素在后半部分,从tail倒着找let current = this.tail;let index = this.length - 1;while(index-- > position) {current = current.pre;}return current.data;}
}
测试代码:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, '第三个位置插入本元素');
console.log(list.forwardString());
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
7.indexOf根据某个元素值获取该节点位置
这个和单向链表一样,就是从头找,找不到就返回-1,找到了就对比并返回索引
//根据元素值获取节点位置
indexOf(data) {let current = this.head;let index = 0;while(current) {if(current.data == data) {return index;}else {current = current.next;index++;}}return -1
}
测试代码:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3
8.update更新某个元素
主要思路还是先查找,然后把数据改了就行。查找的时候采用二分查找。
//更新某个元素
update(position, data) {//1.生成节点let newNode = new Node(data);//2.越界判断if(position < 0 || position >= this.length) return false;//3.寻找位置,改变元素值,二分查找let current = null;if(position < this.length / 2) {current = this.head;let index = 0;while(index++ < position) {current = current.next;}}else {current = this.tail;for(let i = this.length-1; i > position; i--) {current = current.pre; }}current.data = data;return current.data;
}
测试代码:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3
//测试更新
list.update(2,'DJ');
console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht
9.removeAt删除某个位置的节点
这里的主要思路是:
1、首先判断是否只有一个节点,如果只有一个节点,删除后head和tail都要置空.
2、如果有多个节点,要看删除的是第一个、最后一个、其他位置。
3、第一个位置,先清空第二节节点指向被删除元素的pre指针(清空所有指向它的指针,这样该节点在内存中没用途,会被自动回收),然后直接让head指向第二个节点就行了。
4、最后一个位置,同理,删除倒数第二个节点指向被删除节点的next,然后让tail直接指向倒数第二个节点。
5、删除完长度-1
//删除某个位置的元素
removeAt(position) {//1.越界判断if(position < 0 || position >= this.length) return false;let current = this.head; //定义在最上面方便各种情况返回数据//2.判断是否只有一个节点if(this.length == 1) {//2.1如果只有一个节点,那么删除时head和tail都为空this.head = null;this.tail = null;}else {//2.2如果有多个节点,那么就要进行位置判断//2.2.1删除第一个节点if(position == 0) {//删除前head指向的是第一个节点this.head.next.pre = null; //所有指向它的指针都清掉this.head = this.head.next;}//2.2.2删除最后一个节点else if(position == this.length-1) {//删除前tail指向的是最后一个节点current = this.tail;this.tail.pre.next = null; //所有指向它的指针都清掉this.tail = this.tail.pre;}//2.2.3删除其他的节点else {//先找到位置let index = 0;while(index++ < position) {current = current.next;}current.pre.next = current.next;current.next.pre = current.pre;}}//3.删除完-1this.length -= 1;return current.data;
}
代码测试:
const list = new DoublyLinedList();
list.append('DantinZhang');
list.append('努力的但丁');
list.append('zzy');
list.append('ht');
list.insert(2, 'No.3');
console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy ht
console.log(list.backwardString());
console.log(list.get(2)); //第三个位置插入本元素
console.log(list.get(3)); //zzy
console.log(list.indexOf('zzy'));//3
//测试更新
list.update(2,'DJ');
console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht
list.removeAt(2);
//测试删除
console.log(list.toString()); //DantinZhang 努力的但丁 zzy ht
10.remove删除某个元素
//删除某个元素
remove(data) {return this.removeAt(this.indexOf(data));
}
测试代码:
const list = new DoublyLinedList();list.append('DantinZhang');list.append('努力的但丁');list.append('zzy');list.append('ht');list.insert(2, 'No.3');console.log(list.forwardString());//DantinZhang 努力的但丁 No.3 zzy htconsole.log(list.backwardString());console.log(list.get(2)); //第三个位置插入本元素console.log(list.get(3)); //zzyconsole.log(list.indexOf('zzy'));//3//测试更新list.update(2,'DJ');console.log(list.toString()); //DantinZhang 努力的但丁 DJ zzy ht //测试删除list.removeAt(2);list.remove('努力的但丁');console.log(list.toString()); //DantinZhang zzy ht
11.其他方法
//删除某个元素
remove(data) {return this.removeAt(this.indexOf(data));
}//测试是否为空
isEmpty() {return this.length == 0;
}//输出长度
size() {return this.length;
}//获取链表第一个元素值
getHead() {return this.head.data;
}//获取链表最后一个元素值
getTail() {return this.tail.data;
}
更详细的图解参考:大佬的博客
相关文章:
数据结构(二):单向链表、双向链表
数据结构(二)一、什么是链表1.数组的缺点2.链表的优点3.链表的缺点4.链表和数组的区别二、封装单向链表1. append方法:向尾部插入节点2. toString方法:链表元素转字符串3. insert方法:在任意位置插入数据4.get获取某个…...
COCO物体检测评测方法简介
本文从ap计算到map计算,最后到coco[0.5:0.95:0.05] map的计算,一步一步拆解物体检测指标map的计算方式。 一、ap计算方法 一个数据集有多个类别,对于该数据库有5个gt,算法检测出来10个bbox,对于人这个类别来说检测有…...
记一次上环境获取资源失败的案例
代码结构以及资源位置 测试代码 RestController RequestMapping("/json") public class JsonController {GetMapping("/user/1")public String queryUserInfo() throws Exception {// 如果使用全路径, 必须使用/开头String path JsonController.class.ge…...
实战超详细MySQL8离线安装
在RedHat中,RPM Bundle 方式安装MySQL8。建议一定要用 RPM Bndle 版本安装,包全。官网下载:https://dev.mysql.com/downloads/mysql/1.卸载mariadb,会与MySQL安装冲突。rpm -qa | grep mariadb 查看有无mariadb如果有࿰…...
依赖倒置原则|SOLID as a rock
文章目录 意图动机:违反依赖倒置原则解决方案:C++中依赖倒置原则的例子依赖倒置原则的优点1、可复用性2、可维护性在C++中用好DIP的标准总结本文是关于 SOLID as Rock 设计原则系列的五部分中的 最后一部分。 SOLID 设计原则侧重于开发 易于维护、可重用和可扩展的软件。 在…...
Webpack的知识要点
在前端开发中,一般情况下都使用 npm 和 webpack。 npm是一个非常流行的包管理工具,帮助开发者管理项目中使用的依赖库和工具。它可以方便地为项目安装第三方库,并在项目开发过程中进行版本控制。 webpack是一个模块打包工具ÿ…...
handler解析(2) -Handler源码解析
目录 基础了解: 相关概念解释 整体流程图: 源码解析 Looper 总结: sendMessage 总结: ThreadLocal 基础了解: Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一…...
【算法】kmp
KMP算法 名称由来 是由发明这个算法的三个科学家的名称首字母组成 作用 用于字符串的匹配问题 举例说明 字符串 aabaabaaf 模式串 aabaaf 传统匹配方法 第一步 aabaabaaf aabaaf 此时,b和f不一致,则把模式串从头和文本串的第二个字符开始比 第…...
git 常用命令之 git checkout
大家好,我是 17。 git checkout 是 git 中最重要最常用的命令之一,本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…...
一些常见错误
500状态码: 代表服务器业务代码出错, 也就是执行controller里面的某个方法的过程中报错, 此时在IDEA的控制台中会显示具体的错误信息, 所以需要去看IDEA控制台的报错404状态码: 找不到资源找不到静态资源 检查请求地址是否拼写错误 检查静态资源的位置是否正确 如果以上都没有问…...
[单片机框架][调试功能] 回溯案发现场
程序莫名死机跑飞,不知道问题,那么下面教你回溯错误源 回溯案发现场一、修改HardFault_Handler1. xx.s 在启动文件,找到HardFault_Handler。并修改。2. 定义HardFault_Handler_C函数。(主要是打印信息并存储Flash)3. 根…...
MySQL主从同步-(二)搭建从机服务器
在docker中创建并启动MySQL从服务器:**端口3307docker run -d \-p 3307:3306 \-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \-v /atguigu/mysql/slave1/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD123456 \--name atguigu-mysql-slave1 \mysql:8.0.3创建MyS…...
Linux系列 备份与分享文档
作者简介:一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.备份与分享文档 1.使用压缩和解压缩工具 (1&…...
SNI生效条件 - 补充nginx-host绕过实例复现中SNI绕过的先决条件
文章目录1.前置环境搭建2.测试SNI生效条件(时间)3. 证书对SNI的影响3.1 双方使用同一个证书:3.2 双方使用不同的证书与私钥4. 端口号区分测试4.1 端口号区分,证书区分:4.2 端口号区分,证书不区分:5.总结SNI运行机制6. SNI机制绕过…...
傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)
阅读了Modular Routing Design for Chiplet-based Systems这篇论文,是关于多chiplet通信的,个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system,而不仅仅是考虑单个intra-chiplet的局部NoC可以通信,具体的一些…...
C语言静态库、动态库的封装和注意事项
1、动态库、静态库介绍 参考博客:《静态库和动态库介绍以及Makefile》; 2、代码目录结构和编译脚本 参考博客:《实际工作开发中C语言工程的目录结构分析》; 3、编写库的流程 (1)明确需求:需求是否合理、需求的使用场景、需求可能遇…...
MyBatis-Plus分页插件和MyBatisX插件
MyBatis-Plus分页插件和MyBatisX插件六、插件1、分页插件a>添加配置类b>测试八、代码生成器1、引入依赖2、快速生成十、MyBatisX插件1、新建spring boot工程a>引入依赖b>配置application.ymlc>连接MySQL数据库d>MybatisX逆向生成2、MyBatisX快速生成CRUD申明…...
年前无情被裁,面试大厂的这几个月…
2月份了,金三银四也即将来临,在这个招聘季,大厂也开始招人,但还是有很多人吐槽说投了很多简历,却迟迟没有回复… 另一面企业招人真的变得容易了吗?有企业HR吐槽,简历确实比以前多了好几倍&…...
基于Java的分片上传功能
起因:最近在工作中接到了一个大文件上传下载的需求,要求将文件上传到share盘中,下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。 一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件,…...
KDS安装步骤
KDS kinetis design studio 软件 第一步官网(https://www.nxp.com/ 注册账号下载set成功下载软件。 随着AI,大数据这些技术的快速发展,与此有关的知识也普及开来。如何在众多网站中寻找最有价值的信息,如何在最短的时间内获得最新的技…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
