万字深度剖析——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)//如果每一项都满足条件,结果才是true
arr.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===0 return 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=Decnumber let 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=edcnumber while(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]=data this. #count++ } front() { return this.#items[this.#lowCout] } isEmpty() { return this.size()===0 } size() { return this.#count-this.#lowCout } clear() { this.#items={} this.#lowCout = 0 this.#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 #lowcount removeFont(){ 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=false break; } } } 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.head while(current.next!==null){ current =current.next } current.next=node } this.count++ }
-
3,删除分为两种:删除特定位置,删除特点元素数据
-
删除特定位置
-
//指定位置删除 removeAt(index){ if(index>=0&&index<this.count){ let current=this.head if(index===0){ this.head=this.head } else{ let previous for(let i=0;i<index;i++){ previous=current current=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.head for(let i=0;i<index;i++){ node=node.next } return node } }
在removeAt函数中precious调用函数接收节点
-
removeAt(index){ if(index>=0&&index<this.count){ let current = this.head if(index===0){ this.head=this.head } else{ let previous=this.getNodeAt(index-1) previous.next=current previous.next=current.next } this.current-- return current.element } }
上面是根据节点位置删除,如果你想删除一个数据就需要下面的方法
-
规定两个方法,第一个用于比对节点中你想找到的数值,并返回索引值
-
indexOf(element){ let current =this.head for(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.head node.next=current this.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//保护head node.next=current//当前头节点作为新节点的下一个 this.head=node//把新节点作为头节点 }else{ const previous=getNodeAt(index-1) const current=previous.next node.next=current previous.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=node this.tail=node }else{ this.tail.next=node node.prev=node this.tail=node }
如果头部为空,就直接加
-
头部不为空
-
让链表尾部next与新节点连接,新节点prev指向尾部,再让尾部彻底等于新节点
-
insert
- 不说了,思想全在图里
-
insert(element,index){ const node=DoublyLinked(element) let current=this.head if(index>=0&&index<=this.count){ if(index===0){ if(this.head===null) this.head=node this.tail=node } else{ node.next=this.head this.head.prev=node this.head=node } } else if(index===this.count){ current=this.tail current.next=node node.prev=current this.tail=node } else{ const previous=getNodeAt(index-1) current=previous.next node.next=current current.prev=node previous.next=node node.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.head if(index===0){ this.head=current.next if(this.count===1){ this.tail===null }else{ this.head.prev=undefined } } if(index===this.size()-1){ let current =this.tail current.prev=this.tail this.tail.next=undefined } else{ let previous=this.getNodeAt(index-1) current=previous.next previous.next=current.next current.next.prev=previous } this.count-- return current.element } }
remove
-
按照数值删除和单链表方法一样
-
循环链表
-
就是让最后节点的next指向head,循环起来
-
insert插入
-
insert(element,index){ let node=new DoublyLinked(element) let current=this.head if(index>=0&&index<=count){ if(index===0){ if(head===0){ this.head=node node.next=this.head } else{ node.next=current current = this.getNodeAt(this.size() - 1)//不能保证后面的节点指向head,下面解决,获得尾部元素 this.head=node// current.next=this.head//让尾部元素next指向head,保证循环 } }else{ const previous=this.getNodeAt(index-1) current=previous.next previous.next=node node.next=current } this.count++ return true } return false }
removedAt——按索引删除
-
removeAt(index){ if(index>=0&&index<count){ let current=this.head if(index===0){ if(this.size()===1){ let last=this.getNodeAt(this.size()-1) this. head=this.head.next last.next=head } } else{ let previous=this.getNodeAt(index-1) current=previous.next previous.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]=element return 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]=value return 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=key this.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=111 this.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 } //put set (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就不会占内存,也就不会被遍历
- 删除节点后,应该减少链表的节点计数