万字深度剖析——JS数据结构(上)
数组本质是对象,键就是索引,值就是元素。
push /unshift 在数组最后/最前添加
pop /shift 把数组最后/最前的元素删除,返回的是被删除的元素
splice(0,2,5)从第0给位置开始删除2个元素,并添加一个元素
数组自带的排序方法
let arr=[2,5,4,3,1]
arr.sort((y,x)=>x-y)x-y>0交换顺序,不>0就不会交换顺序
最后结果时[2,3,4,5,6]顺序
arr.sort(x,y)
迭代方法 every some filter map foreach reduce
arr.every((item)=>item>12)//如果每一项都满足条件,结果才是truearr.some((item)=>item>12)//只要有其中一项满足条件,结果就是true
map映射,简单来说,就像是一个“翻译器”或者“转换器”。它的主要工作就是将一种东西(比如数字、文字、对象等)按照一定的规则转换成另一种东西。
举个例子,假设你有一个列表,里面装着一些数字:[1, 2, 3, 4, 5]。现在你想把每个数字都变成它的两倍。那么,你可以使用map映射来实现这个目标。map映射会遍历这个列表,把每个数字都拿去乘以2,然后返回一个新的列表:[2, 4, 6, 8, 10]。
再比如,你有一个装着名字的列表:[“张三”, “李四”, “王五”]。你想把每个名字都变成“你好,XXX”的格式。使用map映射,你可以很容易地得到一个新的列表:[“你好,张三”, “你好,李四”, “你好,王五”]。
所以,map映射就是一个非常方便的工具,可以帮助我们快速地对一组数据进行统一的转换或处理。
forEach
arr.forEach((item,index)=>console.log(item,index))//返回数组里每个元素和索引值
reduce
第一个元素是上一次执行的返回值
let res=arr.reduce((item1,item2)=item1+item2)//将每一个元素累加
迭代器对象
每一个数组都有一个迭代器
let ite=arr[Symbol.iterator]()
如上拿到迭代器接口
只要符合迭代器对象就能使用for of、循环遍历对象
for(let i of arr){console.log(i); }
console.log(arr.entries())
结果是迭代器对象
就能拿到键值对
for(let i of arr.entries()){console.log(i);}
拿到键
for(let i of arr.keys()){console.log(i);}
拿到元素的值
for(let i of arr.values()){console.log(i);}
Arry.from将一个类似于数组的对象转化为真的数组结构
function test(){console.log(Array.from(arguments));}
test(1,2,3)
虽然test函数没有形参,但是用arguments可以找到,但是arguments是一个对象用from可以转化成对象
搜索 indexof lastIndexof find findIndex findLast findLastIndex includes
indexof
如果包含就返回正确的索引值,如果不包含就返回-1
let arr=[12,22,23]
arr.indexOf(15)
//结果是-1
includes
包含就返回true,不包含返回false
arr.includes(15)
find能返回最先满足条件的元素
findLast从后面开始查找
findIndex返回索引值
findLastIndex找出数组里最后一个大于10的元素
let res2=arr.findLastIndex(item=>item>10)
栈结构的封装
定义一个类,初始化一个元素。
class stack{constructor(){this.item=[]}}let stack =new stack()
现在可以直接跳过constructor
class stack{items=[]}
-
pop() 方法:
pop()
方法应该是从栈中移除并返回最后一个元素,而不是添加元素。因此,pop()
方法不需要参数,并且应该调用this.items.pop()
来移除数组中的最后一个元素。
-
peek() 方法:
peek()
方法用于查看栈顶元素,但不移除它。因此,peek()
方法也不需要参数,并且应该返回this.items[this.items.length - 1]
,而不是调用this.items.peek()
。因为this.items
是一个数组,而数组没有peek()
方法。push(data)
方法- 接受一个参数
data
,并将其添加到栈顶。 - 返回undefined
-
栈
-
class stack {items = []pop(){this.items.push()}push(){this.items.push(data)}peek(data){return this.items[this.items.length-1]}isEmpty(){// return this.items.length===0return this.items.at(-1)}size(){return this.items.length}clear(){return this.items=[]}Tostring(){return this.items.join(',')}}let stack =new stack()
由于我们可以随便用比如this.item.splice(0,2,9)的方式破坏栈,可以加下划线
_items = []
表示栈的私有性,来保护栈里的元素
-
直至发布es13后才支持自带的属性用在前面加井号的方式
-
#items = []
每次访问stack.#items.push()用stack里面的方法才能改变栈的元素。直接stack.item会报错.
-
用栈方法应用——解决十进制转换
-
每一次把一个十进制的整数除以2的余数用push方法把余数push到栈里越往后的余数月堆到栈顶,再用pop方法把每次余数取出来,也就是从最后的余数取出
let stack =new stack()function Dec(Decnumber){let remStack=new stack()let number=Decnumberlet string=""while(number>0){remStack.push(number)number=Math.floor(number%2)}while(!(remStack.isEmpty())){string+=remStack.pop()}return string}Dec(50)
限制进制数如:8
-
进制数如果是16,但是没有定义用ABCDEF表示10-16就需数组或者字符串定义
-
function ECD(edcnumber){let string=''let rank=new stack()let number=edcnumberwhile(number>0){rank.push(number%2)number=Math.floor(number / 2) }while(!(rank.isEmpty())){string+=rank.pop()}return string} EDC(500,16)//让500按照16进制转换
队列
-
先进先出:像日常做核酸,排在列队头的人做完出去,队尾可以随时加人
-
队列和栈差不多,不同的在于
-
dequeue:返回队头被删除的元素,方法里面用shift来删除第一个元素
-
enqueue:表示队尾添加
-
front:返回队头
class stack{#item=[] dequeue(){ return this.#item.shift()}enqueue(data){this.#item.push(data)}front(){return this.#item.push.at(0)}isEmpty(){return this.#item.length===0}size(){return this.#item.length}clear(){ this.#item=[]}Tostring(){return this.#item.join(" ")}}
但是:用shift的缺点是删除第一个元素就使后面的元素都往后移动一个。若数据太多对渲染效果有害,而delet方法使删除的元素位置上是empty。
-
class Queue{#items={}#lowCout=0//记队头的索引值#count=0//为每次改变往后移记录索引值dequeue() {if(this.isEmpty()){return undefined}//防止出现size负数let res=this.#items[this.#lowCout]delete this.#items[this.#lowCout]this.#lowCout++return res}enqueue(data) {this.#items[this.#count]=datathis. #count++}front() {return this.#items[this.#lowCout]}isEmpty() {return this.size()===0}size() {return this.#count-this.#lowCout}clear() {this.#items={}this.#lowCout = 0this.#count = 0}Tostring() {}}
-
最应该采用对象obj的方式,来模拟数组
obj={0:1,1:2,3:3}
那么数据结构就会改变,所以之前的队列就不能用了。
-
队列应用-——击鼓传花
-
首先由几个玩家,规定一轮击鼓数为7.转为编程思想就是由队头被删并加在队尾,队列轮回后到谁时第七个结束谁出局。首先定义几个玩家,和击鼓传花数
-
game(['kerwin','xiaoming','tiechui','gangaer','guludunzi'],7)
封装game函数
-
1,new一个队列
-
let queue=new Queue()
-
2,邀请玩家入场!
-
for(let i=0;i<list.length;i++){queue.enqueue(list[i]) }
-
3,开始游戏!
-
while(queue.size>1){for(let i=0;i<num;i++){queue.enqueue( queue.dequeue())}
4,获胜者出列颁奖!
-
return queue.dequeue()
完整代码
-
function game(list,num){ let queue=new Queue() for(let i=0;i<list.length;i++){queue.enqueue(list[i]) } while(queue.size>1){for(let i=0;i<num;i++){queue.enqueue( queue.dequeue())}console.log(queue.dequeue(),"淘汰了"); } return queue.dequeue() } game(['kerwin','xiaoming','tiechui','gangaer','guludunzi'],7)
双端队列
-
可以从队头出,也可以从队头进,可以从队尾出,也可以从队尾进。例如,日常排队从队头插队,也可以看队太长了从队尾直接离开
-
把方法改为从头加addFront
-
分为三个情况:
-
lowcount(开头索引值)=0;把数组中后面的元素向后移动一位,把第一个位置空出来。然后在第一个位置上添加数据data。
-
else{//lowcount从0开始for(let i=this.#count;i>0;i--){this.#items[i]=this.#items[i-1]}this.#items[0]=data //0的位置被移出来,就可以添加数据this.#count++}
-
lowcount(开头索引值)>0;把lowcount减一,留出0的位置。添加data
-
if(this.#lowCout>0){this.#lowCout--this.#items[this.#lowCout]=data
-
数组为空就可以直接添加复用addback方法。
-
//如果为空if(this.isEmpty()){this.addBack(data)}
还有新颖的是在队尾删除
-
因为count是从0开始计数,最后一个元素队尾元素的索引是
#count - 1
,因为数组索引从0开始。因此,#count
本身并不直接指向队尾元素,而是指向队尾元素之后的下一个位置(即队列的长度)。removeBack(){if(this.isEmpty()){return}this.#count--let res=this.#items[this.#count]delete this.#items[this.#count]return res}
其他和单队列差不多
-
class Queue{#items#count=0#lowcountremoveFont(){let res=this.#items[this.#lowcount]delete this.#items[this.#lowcount]return res}addBack(){this.#items.this.#count=[data]this.count++}addFront(){if(this.isEmpty()){this.#items.addBack()}else{if(this.#lowcount>0){this.#lowcount--this.#items[this.#lowcount]=[data]}else {for(let i=0;i<this.#count;i++)this.#items[i+1]=this.#items[i]for(let i=this.#count;i>0;i--)this.#items[i]=this.#items[i-1]}}}removeBack(){if(this.isEmpty()){return}this.#count--let res=this.#items[this.#count]delete this.#items[this.#count]return res}isEmpty(){return this.size()===0}size(){return this.#count-this.#lowcount}peekFront(){return this.#items[this.#lowcount]}peekBack(){if(this.isEmpty()){return }return this.#items[this.#count-1]}toString(){let str=""for(let i=0;i<this.#count;i++){str=`${this.#items[i]}`}return str} } let dequeue=new Queue() function test(str){ const lowstr=str.toLocalLowerCase().split("").join let queue=new Queue() for(let i=0;i<lowstr.length;i++){queue.addBack(lowstr[i]) } console.log(dequeue);}
应用:回文检查如dad
-
:split方法会自动缩进空格
-
function test(str){ const lowstr=str.toLocalLowerCase().split("").join let queue=new Queue() for(let i=0;i<lowstr.length;i++){queue.addBack(lowstr[i]) } while(dequeue.size()>1){if(dequeue.removeBack!==dequeue.removeFont){ isEqual=falsebreak;} } } test('DA D')
链表
-
模拟栈和队列,也有自己独特的魅力
-
在构造函数中设置
next
为null
可以确保每个新创建的节点都默认没有指向下一个节点,合链表节点的初始状态。this.element = element;
这行代码的作用是将传递给构造函数的element
参数的值赋给新创建的节点实例的element
属性。 -
class Node {constructor(element) {this.element = element; // 节点存储的数据this.next = null; // 指向下一个节点的链接} }
-
在linkedList中再定义记录节点位置和头节点
-
1,在constructor构造函数器中用定义并初始化两个变量,cout用来记录链表中的节点,应为要从链头到链尾一点点找,还要初始化一个链头head
-
2,push从后面插入
-
push(){const node = new Node(element)if(head==null){this.head=Node }else{let current=this.headwhile(current.next!==null){current =current.next}current.next=node}this.count++}
-
3,删除分为两种:删除特定位置,删除特点元素数据
-
删除特定位置
-
//指定位置删除removeAt(index){if(index>=0&&index<this.count){let current=this.headif(index===0){this.head=this.head}else{let previousfor(let i=0;i<index;i++){previous=currentcurrent=current.next}previous.next=current.next}this.current--return current.element}}
-
参数检查:
- 为了保护head不被破坏复制给current找节点头
- 首先检查传入的索引
index
是否有效,即是否在链表的范围内(0 到this.count - 1
)。
- 首先检查传入的索引
-
删除头节点:
- 如果
index
为 0,表示要删除的是头节点。在这种情况下,只需要将头节点指向下一个节点即可。
- 如果
-
删除非头节点:
- 如果
index
大于 0,则需要遍历链表找到要删除的节点及其前一个节点。 - 使用两个指针
previous
和current
,其中current
初始指向头节点。 - 遍历链表,直到
current
指向要删除的节点。 - 然后,将
previous
的next
指针指向current
的next
,从而跳过current
节点,实现删除。
- 如果
-
更新计数:
- 删除节点后,应该减少链表的节点计数
this.count
。但在您提供的代码中,这一步被遗漏了。 - 你可能有这样的疑惑
-
为什么
current
是正确的节点 - 初始化时,
current
指向头节点,这是已知的。 - 循环确保了
current
指针从头节点开始,逐个节点移动,直到到达指定的索引位置。 - 循环结束后,
current
指针所指向的节点就是我们要删除的节点,因为我们已经根据index
的值移动了current
指针相应的次数。 -
因此,不需要将
current
复制为index
,因为current
的位置是通过遍历链表来确定的,而不是通过直接设置索引值。循环的结构和current
指针的移动确保了在循环结束时,current
指向的是正确的节点。 -
实验效果,
为简化方法,封装一个函数来接节点
-
getNodeAt(index){if(index>=0&&index.this.count){let node=this.headfor(let i=0;i<index;i++){node=node.next}return node}}
在removeAt函数中precious调用函数接收节点
-
removeAt(index){if(index>=0&&index<this.count){let current = this.headif(index===0){this.head=this.head}else{let previous=this.getNodeAt(index-1)previous.next=currentprevious.next=current.next}this.current--return current.element}}
上面是根据节点位置删除,如果你想删除一个数据就需要下面的方法
-
规定两个方法,第一个用于比对节点中你想找到的数值,并返回索引值
-
indexOf(element){let current =this.headfor(let i=0;i<this.count;i++){if(方法判断(element,current.element)){return i}current=current.next} return -1 }
在这个
indexOf
函数中,如果方法判断(element, current.element)
返回true
,那么return i;
会被执行,函数会返回索引i
,并且不会再执行current = current.next;
或者任何后续的循环迭代。整个函数调用会结束,控制权会返回到调用indexOf
函数的地方。 -
第二个用于接收索引值,调上一个删除方法
-
例如这样一个方法
-
equalFn(a,b){return a===b}
但是如果是一个对象,就不能这样简单对比,因为引用数据类型在栈中存放地址,每个对象地址不一样指向堆内存中实际对象的地址。
-
可以直接暴力的转换成字符串
-
equalFn(a,b){return JSON.stringify(a)===JSON.stringify(b)}
但是如果把两个对象交换位置,就不能判断了
-
可以直接用第三方库imumutable
-
双向链表
-
每个节点即能指向前一个(prev),有能指向后一个(next)
-
插入值
-
如果想在头部插入
-
insert(element,index){if(index>=0&&index<=this.count){const node=new Node(element)if(index===0){const current =this.headnode.next=currentthis.head=node } }
-
if(index === 0){ // 如果插入位置是链表头部
这行代码检查是否要在链表的头部插入新节点。
index
是插入位置的索引,如果它是0,意味着新节点应该成为新的头节点。 -
node.next = this.head; // 新节点的下一个节点设置为当前头节点
这行代码将新节点(
node
)的next
属性设置为当前的头节点(this.head
)。这样,新节点就指向了原来的头节点,保持了链表的连续性。node
:新创建的节点,即将插入链表。this.head
:当前链表的头节点。node.next = this.head
:将新节点的next
指针指向当前的头节点。-
如果想插入的位置不是头部
-
思想:
-
示例
假设链表当前状态如下:
复制
Head -> A -> B -> C
其中,A 是头节点,B 是第二个节点,C 是第三个节点。现在,我们想要在B和C之间插入一个新节点X:
previous = this.getNodeAt(1);
// 获取B的引用current = previous.next;
// 获取C的引用node.next = current;
// 设置X的next指向Cprevious.next = node;
// 设置B的next指向X-
插入后的链表状态:
复制
Head -> A -> B -> X -> C
总结
- 当在链表的特定位置插入新节点时,需要找到该位置的前一个节点和当前节点。
- 将新节点的
next
指向当前节点。 - 将前一个节点的
next
更新为新节点。 - 具体代码
-
else{const previous=this.getNodeAt(index-1)//获取前一个节点const current =previous.next//让前一个节点指向新节点引用的位置node.next=current//把新建的节点放进去previous.next=node}
完整插入代码
-
insert(element,index){if(index>=0&&index<=this.count){const node=new Node(element)if(index===0){const current=this.head//保护headnode.next=current//当前头节点作为新节点的下一个this.head=node//把新节点作为头节点}else{const previous=getNodeAt(index-1)const current=previous.nextnode.next=currentprevious.next=node}this.count++return true}return false }
剩下几个简单的方法
-
isEmpty(){return this.size===0 } size(){return this.count } gethead(){return this.head }
双端链表
-
还可以继承单链表的属性
-
class DoubleNode extends Node {constructor(element) {super(element); // 调用父类Node的构造函数this.prev = null; // 初始化DoubleNode特有的pre属性} }
-
super(element)
调用Node
类的构造函数,并将element
参数传递给父类构造函数。这样,DoubleNode
实例就继承了Node
类的element
属性 -
之前封装的单向链表中很多方法需要重新封装
-
push
-
push(){const node=new DoublyNode()//prev next if(this.head===null){this.head=nodethis.tail=node }else{this.tail.next=nodenode.prev=nodethis.tail=node }
如果头部为空,就直接加
-
头部不为空
-
让链表尾部next与新节点连接,新节点prev指向尾部,再让尾部彻底等于新节点
-
insert
- 不说了,思想全在图里
-
insert(element,index){const node=DoublyLinked(element)let current=this.headif(index>=0&&index<=this.count){if(index===0){if(this.head===null)this.head=nodethis.tail=node}else{node.next=this.headthis.head.prev=nodethis.head=node} }else if(index===this.count){current=this.tailcurrent.next=nodenode.prev=currentthis.tail=node}else{const previous=getNodeAt(index-1)current=previous.nextnode.next=currentcurrent.prev=nodeprevious.next=nodenode.prev=previous}this.count++return true}
removeAt删除
-
注意:index要小于this.count,因为count从0开始,最后的count值没有节点(和insert不同)
-
按照索引值删除和insert思想差不多,需要注意的是如果让head的next指向下一个会出问题,因为曾经的尾部被head占了,所以之前让head直接赋值为head的next—— this.head=current.next
-
removeAt(index){if(index>=0&&index<this.count){let current=this.headif(index===0){this.head=current.nextif(this.count===1){this.tail===null}else{this.head.prev=undefined} }if(index===this.size()-1){let current =this.tailcurrent.prev=this.tailthis.tail.next=undefined}else{let previous=this.getNodeAt(index-1)current=previous.nextprevious.next=current.nextcurrent.next.prev=previous}this.count--return current.element}}
remove
-
按照数值删除和单链表方法一样
-
循环链表
-
就是让最后节点的next指向head,循环起来
-
insert插入
-
insert(element,index){let node=new DoublyLinked(element)let current=this.headif(index>=0&&index<=count){if(index===0){if(head===0){this.head=nodenode.next=this.head}else{node.next=currentcurrent = this.getNodeAt(this.size() - 1)//不能保证后面的节点指向head,下面解决,获得尾部元素this.head=node//current.next=this.head//让尾部元素next指向head,保证循环}}else{const previous=this.getNodeAt(index-1)current=previous.nextprevious.next=nodenode.next=current}this.count++ return true}return false }
removedAt——按索引删除
-
removeAt(index){ if(index>=0&&index<count){let current=this.headif(index===0){if(this.size()===1){let last=this.getNodeAt(this.size()-1)this. head=this.head.nextlast.next=head}}else{let previous=this.getNodeAt(index-1)current=previous.nextprevious.next=current.next//不用怕删除后没有循环,因为被删的current的next就指向head}this.count--return current.element } }
集合set
-
无序且唯一的项组成
-
因为对象的属性不能重复适合作为集合,下面我们用对象模拟
-
属性和属性名都是一个内容,存100就是100:100所以想要操作某个元素直接this.items[element]就可以
-
建一个class封装增删查改
-
class kerwin{// constructor(){// this.items={}// }items={}add(element){if(!this.has(element)){this.items[element]=elementreturn true}return false}delete(element){ if(this.has(element)){delete this.items[element]return true }}has(element){ return element in this.items}clear(){ return this.items={}}size(){ return Object.keys(this.items).length}values(){ return Object.values(this.items)}}
其中size方法用
Object.keys()
方法会返回一个包含对象所有自有属性(不包括原型链上的属性)的键(key)的数组。 -
应用:检查数组中是否有重复元素
-
var arr=[1,2,3,3,3,4]arr.forEach(items=>{myList.add(items)})
Es6中的set属性
-
迭代器
-
符合迭代器的元素可以用多个next遍历,也可以用for...of遍历,也可以用展开运算符变为普通数组
-
与上面我们自己封装的set不同在于,Es6的size是一个属性,我们是方法
-
应用
-
交集,并集,差集——从后端接收数据时候使用
-
var mySetA=new Set([1,2,3])var mySetB=new Set([2,3,4])// 并集var mySetAnd=new Set([...mySetA],[...mySetB])// 交集——只有数组才有filter方法var mySetJiao=new Set([...mySetA].filter(item=>mySetB.has(item)))//差集var mySetJiao=new Set([...mySetA].filter(item=>!mySetB.has(item)))
字典
-
和集合很相似,但集合是【值,值】,字典【键,值】,键可以是对象,也可以是任意数据类型
-
class Dictionary{table={}//将键转化为字符串tostrFn(item){if(item===null){return 'NULL'}else if(item===undefined){return 'UNDEFINDED'}else if(item==='string'||item instanceof String){return item}return JSON.stringify(item)} }
tostrFn
-
:使用
tostrFn
方法将key
转换为一个字符串。这是因为在 JavaScript 对象中,键总是被转换为字符串。tostrFn
方法的执行步骤如下:- 如果
item
(在这里是key
)是null
,返回字符串'NULL'
。 - 如果
item
是undefined
,返回字符串'UNDEFINED'
。 - 如果
item
是字符串或String
对象的实例,直接返回该字符串。 - 否则,使用
JSON.stringify
将item
转换为字符串。
- 如果
-
检查键存在性:使用转换后的键字符串在
this.table
中查找。this.table
是一个对象,用于存储字典的键值对。 -
返回结果:如果找到对应的键(即
this.table
中存在该键),则this.table[this.tostrFn(key)]
不会是null
,haskey
方法返回true
。如果没有找到,或者对应的值是null
,则返回false
。 注意:如果haskey
方法中检查的是!== null
,这意味着如果键存在但对应的值是undefined
或其他非null
值,它也会返回true
。所以改为!=null就可以了-
set
- 检查是否有键
- 第一种方法
//检查是否含有键haskey(key) {return this.table[this.tostrFn(key)] != null}set(key,value){if(key!==null&&value!==null){const tablekey=this.tostrFn(key)this.table[tablekey]=valuereturn true}return false}}
这种方法将键存放的被转化后的字符串键名,所以另起一个方法二将value中存键+值,保证获取到最原始的键
-
方法二
-
set(key,value){if(key!==null&&value!==null){const tablekey=this.tostrFn(key)this.table[tablekey]=new ValuePair(key,value)return true}return false}}var mysseet=Dictionary()class ValuePair{constructor(key,value){this.key=keythis.value=value}}
valuepair类创建一个实例对象保存一份原始的key,value
-
其他方法
-
get(key){const ValuePair=this.table[this.tostrFn(key)]return ValuePair ==null?undefined:ValuePair.value}remove(key){if(this.haskey(key)){delete this.table.tostrFn(key)return true}return false}keys(){return this.keyValues().map(item=>item.key)}values(){return this.keyValues().map(item=>item.value)}keyValues(){return Object.values(this.table)//Object.values() 只返回自有属性的值,不返回继承属性的值。}size(){return Object.keys(this.table).length}isEmpty(){return this.size===0}clear(){return this.table=null}forEach(cb){const ValuePair=this.keyValues()for(let i=0;i<ValuePair.length;i++){cb(ValuePair[i].key,ValuePair[i].value)}}
散列表
-
有上字典可以看到,由于set每次查找键都需要转化为JSON字符串很麻烦,并且遍历上万条大数据很消耗性能,所以散列表可以把键通过散列函数转换为一个数组作为索引,再从散列表中查找
-
set方法(就相当于push)
-
set (key,value){if(key!=null&&value!=null){const position=111this.table[position]=ValuePair(key,value)return true}return false}
需要有一个方法能给table设置类似position=111这个索引数字
-
hash表就派上用场了,将table中的键转化为字符串,
-
如果直接是数字就更好,不用转换,
-
一旦是字符串就用charCodeAt方法转化为ASCALl码
-
,防止hash值过大用取余
-
hashCode(key){const tablekey=this.tostrFn(key)for(let i=0;i<tablekey.length;i++){hash+=tablekey.charCodeAt(i)}return hash%37}//putset (key,value){if(key!=null&&value!=null){const position= hashCode(key)this.table[position]=ValuePair(key,value)return true}return false}
-
其他方法——get remove
-
get(key){ const ValuePair=this.table[this.hashCode(key)] return ValuePair==null?undefined:ValuePair}remove(key){const deletePair=this.table[hash]const hash=this.hashCode(key)if(deletePair!==null){delete this.table[hash]return true}return false}
-
ES6中的map
-
const obj = {q: 235}; map.set(obj, 'aaaaa');
在这段代码中,
obj
是一个常量,引用了一个对象。当你调用map.set(obj, 'aaaaa')
时,你是将obj
这个引用作为键设置到了Map
中。如果你尝试直接使用字面量对象作为键,如下所示:
map.set({q: 123}, 'bbbbb');
这里的问题在于,每次你使用
{q: 123}
这样的对象字面量时,你实际上是在创建一个全新的对象。在JavaScript中,对象是通过引用来比较的,而不是通过值。因此,即使两个对象字面量看起来相同(例如{q: 123}
和另一个{q: 123}
),它们却是不同的引用,因此被视为不同的键。总结来说,不能直接把{q: 123}
传入map
作为键,是因为每次这样写都会创建一个全新的对象,而Map
是通过引用来识别键的。 -
wakemap——对象只能是键
-
有垃圾回收机制,和map不同,一旦object被null,objet就不会占内存,也就不会被遍历
- 删除节点后,应该减少链表的节点计数
相关文章:
万字深度剖析——JS数据结构(上)
数组本质是对象,键就是索引,值就是元素。 push /unshift 在数组最后/最前添加 pop /shift 把数组最后/最前的元素删除,返回的是被删除的元素 splice(0,2,5)从第0给位置开始删除2个元素,并添加一个元素 数组自带的…...
golang dlv调试工具
golang dlv调试工具 在goland2022.2版本 中调试go程序报错 WARNING: undefined behavior - version of Delve is too old for Go version 1.20.7 (maximum supported version 1.19) 即使你go install了新的dlv也无济于事 分析得出Goland实际使用的是 Goland安装目录下dlv 例…...
【算法 C/C++】二维前缀和
2025 - 03 - 08 - 第 70 篇 Author: 郑龙浩 / 仟濹 【二维前缀和】 文章目录 前缀和与差分 - 我的博客前缀和(二维)1 基本介绍(1) **sum[i][j] 表示什么???**(2) **前缀和怎么求???计算 sum[i][j]…...
如何使用postman来测试接口
一、postman的介绍与下载 可参考: https://blog.csdn.net/freeking101/article/details/80774271 二、api获取网站 阿里云API应用市场 地址:云市场_镜像市场_软件商店_建站软件_服务器软件_API接口_应用市场 - 阿里云 三、具体测试过程 可模拟浏览…...
olmOCR:高效精准的 PDF 文本提取工具
在日常的工作和学习中,是否经常被 PDF 文本提取问题困扰?例如: 想从学术论文 PDF 中提取关键信息,却发现传统 OCR 工具识别不准确或文本格式混乱?需要快速提取商务合同 PDF 中的条款内容,却因工具不给力而…...
Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)
1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…...
请谈谈 HTTP 中的重定向,如何处理 301 和 302 重定向?
HTTP重定向深度解析:301与302的正确使用姿势 一、重定向本质解析 重定向就像快递员送快递时发现地址变更,新地址会写在包裹单的"改派地址"栏。 浏览器收到3xx状态码时,会自动前往Location头指定的新地址。 常用状态码对比&…...
隧道定向号角喇叭为隧道安全保驾护航
隧道广播系统的搭建:科技赋能,打造安全高效的隧道环境。隧道作为现代交通网络的重要组成部分,其安全管理和信息传递的效率直接关系到整个交通系统的运行。然而,隧道环境的特殊性——封闭、狭窄、回声干扰多,使得传统的…...
RuleOS:区块链开发的“破局者”,开启Web3新纪元
RuleOS:区块链开发的“破冰船”,驶向Web3的星辰大海 在区块链技术的浩瀚宇宙中,一群勇敢的探索者正驾驶着一艘名为RuleOS的“破冰船”,冲破传统开发的冰层,驶向Web3的星辰大海。这艘船,正以一种前所未有的姿…...
C#程序结构及基本组成说明
C# 程序的结构主要由以下几个部分组成,以下是对其结构的详细说明和示例: 1. 基本组成部分 命名空间 (Namespace) 用于组织代码,避免命名冲突。通过 using 引入其他命名空间。 using System; // 引入 System 命名空间类 (Class) C# 是面向对象的语言,所有代码必须定义在类或…...
Django与数据库
我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲alpha策略制定后的测试问题 mysql配置 Django模型体现了面向对象的编程技术,是一种面向对象的编程语言和不兼容类型能相互转化的编程技术,这种技术也叫ORM&#…...
力扣热题 100:二叉树专题进阶题解析(后7道)
系列文章目录 力扣热题 100:哈希专题三道题详细解析(JAVA) 力扣热题 100:双指针专题四道题详细解析(JAVA) 力扣热题 100:滑动窗口专题两道题详细解析(JAVA) 力扣热题 100:子串专题三道题详细解析(JAVA) 力…...
Linux——system V共享内存
共享内存区是最快的IPC(进程内通信)形式,不再通过执行进入内核的系统调用来传递彼此的数据 1.共享内存的原理 IPC通信的本质是让不同的进程先看到同一份资源,然后再进行通信,所以想要通过共享内存进行通信,那么第一步一定是让两个…...
【C语言】指针篇
目录 C 语言指针概述指针的声明和初始化声明指针初始化指针 指针的操作解引用操作指针算术运算 指针的用途动态内存分配作为函数参数 指针与数组数组名作为指针通过指针访问数组元素指针算术和数组数组作为函数参数指针数组和数组指针指针数组数组指针 函数指针函数指针的定义和…...
XGBoost介绍
XGBoost:是eXtreme Gradient Boosting(极端梯度提升)的缩写,是一种强大的集成学习(ensemble learning)算法,旨在提高效率、速度和高性能。XGBoost是梯度提升(Gradient Boosting)的优化实现。集成学习将多个弱模型组合起来,形成一个…...
力扣:找到一个数字的 K 美丽值(C++)
一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目: 子字符串长度为 k 。子字符串能整除 num 。 给你整数 num 和 k ,请你返回 num 的 k 美丽值。 注意: 允许有 前缀 0 。0 不能整除任何值。 一个 子字符串 是一个字符串里…...
数据结构:有序表的合并
前文介绍了《有序表的插入》,本文介绍有序表的合并。这两种对有序表的操作,是数据结构中常考的内容,特别是在 408 考卷中,在算法设计的题目中,有可能会考查对有序表的操作。那么,这两篇文章中的方法就是能够…...
AI写论文提示词指令大全,快速写论文
目录 一、十大学术写作提示词1、研究主题2、研究问题3、论文架构4、学术论证5、文献关键要素6、专业文本可读性转换7、学术语言规范化8、提高语言准确性9、多维度、深层论证10、优化文本结构 二、快速写论文提示词1、确认研究选题2、整理相关资料3、快速完成论文大纲4、整合文献…...
物联网IoT系列之MQTT协议基础知识
文章目录 物联网IoT系列之MQTT协议基础知识物联网IoT是什么?什么是MQTT?为什么说MQTT是适用于物联网的协议?MQTT工作原理核心组件核心机制 MQTT工作流程1. 建立连接2. 发布和订阅3. 消息确认4. 断开连接 MQTT工作流程图MQTT在物联网中的应用 …...
【从零开始学习计算机科学】计算机组成原理(七)存储器与存储器系统
【从零开始学习计算机科学】计算机组成原理(七)存储器与存储器系统 存储器存储器相关概念存储器分类存储器系统存储器性能指标存储器层次概述程序访问的局部性原理SRAM存储器存储器的读写周期DRAM存储器DRAM控制器高性能的主存储器存储器扩展只读存储器ROM光擦可编程只读存储…...
ctf-WEB: 关于 GHCTF Message in a Bottle plus 与 Message in a Bottle 的非官方wp解法
Message in a Bottle from bottle import Bottle, request, template, runapp Bottle()# 存储留言的列表 messages [] def handle_message(message):message_items "".join([f"""<div class"message-card"><div class"me…...
Java集合_八股场景题
Java集合 在Java开发中,集合框架是面试和实际开发中非常重要的内容。以下是一些常见的Java集合八股文问题和场景题,以及详细答案和示例代码。 1. Java集合框架的结构是什么? 答案: Java集合框架主要分为三大接口:Col…...
Scaled_dot_product_attention(SDPA)使用详解
在学习huggingFace的Transformer库时,我们不可避免会遇到scaled_dot_product_attention(SDPA)这个函数,它被用来加速大模型的Attention计算,本文就详细介绍一下它的使用方法,核心内容主要参考了torch.nn.functional中该函数的注释…...
SpringBoot(一)--搭建架构5种方法
目录 一、⭐Idea从spring官网下载打开 2021版本idea 1.打开创建项目 2.修改pom.xml文件里的版本号 2017版本idea 二、从spring官网下载再用idea打开 三、Idea从阿里云的官网下载打开 编辑 四、Maven项目改造成springboot项目 五、从阿里云官网下载再用idea打开 Spri…...
初识大模型——大语言模型 LLMBook 学习(一)
1. 大模型发展历程 🔹 1. 早期阶段(1950s - 1990s):基于规则和统计的方法 代表技术: 1950s-1960s:规则驱动的语言处理 早期的 NLP 主要依赖 基于规则的系统,如 Noam Chomsky 提出的 生成语法&…...
Array and string offset access syntax with curly braces is deprecated
警告信息 “Array and string offset access syntax with curly braces is deprecated” 是 PHP 中的一个弃用警告(Deprecation Notice),表明在 PHP 中使用花括号 {} 来访问数组或字符串的偏移量已经被标记为过时。 背景 在 PHP 的早期版本…...
27. Harmonyos Next仿uv-ui 组件NumberBox 步进器组件禁用状态
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! 文章目录 1. 组件介绍2. 效果展示3. 禁用状态设置3.1 整体禁用3.2 输入框禁用3.3 长按禁用 4. 完整示例代码5. 知识点讲解5.1 禁用状态属性5.2 禁用…...
Java高频面试之集合-08
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:详细说说CopyOnWriteArrayList CopyOnWriteArrayList 详解 CopyOnWriteArrayList 是 Java 并发包(java.util…...
做到哪一步才算精通SQL
做到哪一步才算精通SQL-Structured Query Language 数据定义语言 DDL for StructCREATE:用来创建数据库、表、索引等对象ALTER:用来修改已存在的数据库对象DROP:用来删除整个数据库或者数据库中的表TRUNCATE:用来删除表中所有的行…...
SpringAI介绍及本地模型使用方法
博客原文地址 前言 Spring在Java语言中一直稳居高位,与AI的洪流碰撞后也产生了一些有趣的”化学反应“,当然你要非要说碰撞属于物理反应也可以, 在经历了一系列复杂的反应方程后,Spring家族的新成员——SpringAI,就…...
空指针异常的触发
面向对象分析: 当你要吃饭,饭是对象,提供吃饭这个功能,所以饭为null时,你去调吃饭这个功能,就是去操作饭这个抽象模型,但这个模型是null,就是空指针异常了,但如果有了饭…...
尚硅谷爬虫note15n
1. 多条管道 多条管道开启(2步): (1)定义管道类 (2)在settings中开启管道 在pipelines中: import urllib.request # 多条管道开启 #(1)定义管道类 #(2)在setti…...
基于SSM+Vue的汽车维修保养预约系统+LW示例
1.项目介绍 系统角色:管理员、员工、用户功能模块:用户管理、员工管理、汽车类型管理、项目类型管理、维修/预约订单管理、系统管理、公告管理等技术选型:SSM,vue(后端管理web),Layuiÿ…...
【商城实战(13)】购物车价格与数量的奥秘
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
在线json转ArkTs-Harmonyos
轻松将 JSON 数据转换为类型安全的 ArkTs 接口。快速准确地生成代码,提升开发效率,告别手动编写,让您的开发流程更加流畅! gotool...
Cannot resolve symbol ‘view‘ Androidstudio报错解决办法
报错原因 出现 Cannot resolve symbol view 错误是因为代码中的 view 变量未正确定义或不在当前作用域内。以下是常见场景和解决方法: 场景 1:在 点击事件监听器 中获取 view 如果代码在 OnClickListener 的 onClick 方法中,view 是方法的参…...
三级缓存架构
三级缓存架构是一种通过分层缓存设计来优化系统性能、降低数据库负载、提高数据访问效率的解决方案,尤其适用于高并发、高吞吐量的业务场景(如电商、社交平台、实时推荐等)。其核心思想是通过多级缓存逐层过滤请求,减少对底层存储…...
webshell一些上传心得
我们以upload-labs为基础 一、前端拦截: 如第一关 工作方式: 直接在前端拦截 绕过方式: 因为没有限制后端,所有可以用bs 绕过前端修改格式即可 将需要上传的php文件改成jpg格式 使用burp suite 拦截上传后,使用re…...
doris:阿里云 MaxCompute
MaxCompute 是阿里云上的企业级 SaaS(Software as a Service)模式云数据仓库。 什么是 MaxCompute 连接 MaxCompute 示例 -- 1. 创建Catalog。 CREATE CATALOG mc PROPERTIES ("type" "max_compute","mc.default.projec…...
MyBatis-Plus 分页查询接口返回值问题剖析
在使用 MyBatis-Plus 进行分页查询时,很多开发者会遇到一个常见的问题:当分页查询接口返回值定义为 Page<T> 时,执行查询会抛出异常;而将返回值修改为 IPage<T> 时,分页查询却能正常工作。本文将从 MyBatis-Plus 的分页机制入手,详细分析这一问题的根源,并提…...
【面试】框架
框架 1、介绍一下Spring 的 IOC2、将一个类声明为 Bean 的注解有哪些3、Bean 的作用域有哪些4、Spring 框架中的 Bean 是线程安全的吗5、Spring 容器对象的懒加载6、Spring 容器中的 bean 生命周期7、谈谈自己对于 Spring DI 的了解8、注入 Bean 的注解有哪些9、Spring Boot 如…...
MWC 2025 | 紫光展锐联合移远通信推出全面支持R16特性的5G模组RG620UA-EU
2025年世界移动通信大会(MWC 2025)期间,紫光展锐联合移远通信,正式发布了全面支持5G R16特性的模组RG620UA-EU,以强大的灵活性和便捷性赋能产业。 展锐芯加持,关键性能优异 RG620UA-EU模组基于紫光展锐V62…...
《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功: 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具:Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…...
Mysql内置函数
日期函数: 例如: select current_date();---打印日期 select current_time()----打印时间 select current_timestamp();打印时间戳 select now(); ----日期时间 select date(‘2020-10-01 00:00:00’); ----提取日期进行打印: 也可以配合…...
一文了解汽车图像传感器
2024年底,安森美做了题为"How Automotive Image Sensors Transform the Future of Autonomous Driving"的演讲,这里结合其内容对自动驾驶图像传感器做一个介绍。 当前的自动驾驶感知技术主要有两大技术路线:一种是仅使用摄像头作为传感器进行信息采集的纯…...
cocos creator使用mesh修改图片为圆形,减少使用mask,j减少drawcall,优化性能
cocos creator版本2.4.11 一个mask占用drawcall 3个以上,针对游戏中技能图标,cd,以及多玩家头像,是有很大优化空间 1.上代码,只适合单独图片的,不适合在图集中的图片 const { ccclass, property } cc._decorator;c…...
面向高质量视频生成的扩散模型方法-算法、架构与实现【附核心代码】
目录 算法原理 架构 代码示例 算法原理 正向扩散过程:从真实的视频数据开始,逐步向其中添加噪声,随着时间步 t 的增加,噪声添加得越来越多,最终将原始视频数据变成纯噪声。数学上,t 时刻的视频数据与 t…...
vue3框架的响应式依赖追踪机制
当存在一个响应式变量于视图中发生改变时会更新当前组件的所以视图显示,但是没有视图中不写这个响应式变量就就算修改该变量也不会修改视图,这是为什么?我们能否可以理解宽泛的理解为vue组件的更新就是视图的更新,单当视图中不存在…...
Ubuntu本地部署Open manus(完全免费可用)
目录 1.介绍 2.环境搭建 3.更改配置 4.运行 1.介绍 关于由于邀请码的限制,导致很多用户无法顺利体验manus带来的ai agent体验,但是最近开源的open manus是另一个不错的选择。 先来看看运行的结果: 我让open manus帮我打开小米的主页&am…...
MacOS Big Sur 11 新机安装brew wget python3.12 exo
MacOS Big Sur 11,算是很老的系统了,所以装起来brew有点费劲。 首先安装brew 官网: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 官网加速: 按照官网的方法࿰…...