Redis系列二:位图实战,实现打卡签到

Redis系列二:位图实战,实现打卡签到

前言如果要统计一篇文章的阅读量,可以直接使用Redis的incr指令来完成。如果要求阅读量必须按用户去重,那就可以使用set来记录阅读了这篇文章的所有用户id,获取set集合的长度就是去重阅读量。但是如果爆款文章阅读量太大,set会浪费太多存储空间。这时候我们就要使用Redis提供的HyperLogLog数据结构来代替set,它只会占用最多12k的存储空间就可以完成海量的去重统计。但是它牺牲了准确度,它是模糊计数,误差率约为0.81%。

按照官网的说法,Redis位图Bitmaps不是实际的数据类型,而是在字符串类型上定义的一组面向位的操作。在Redis中字符串限制最大为512MB,所以位图中最大可以设置2^32个不同的位(42.9亿个)。图位的最小单位是比特(bit),每个bit的值只能是0或1。位图适合存bool数据,当某个业务只有两种结果的时候,位图是不二之选位图的存储大小计算:(maxOffset/8/1024/1024)MB。其中maxOffset为位图的最大位数

基本操作

//bit基本操作asyncfunctionbaseBitFun(){//hello=>二进制【0110100001100101011011000110110001101111】const[bitKey,bitValue]=[‘bitKey’,‘hello’]client.set(bitKey,bitValue,redis.print);client.get(bitKey,redis.print);//1.根据偏移量获取bit上的值0=》0;1-》1client.getbit(bitKey,1,redis.print);//2.bitcount获取全部的1的总数client.bitcount(bitKey,redis.print);//3.setbit设置指定偏移量的值,0||1//!offset参数必须0到2^32(bit映射被限制在512MB之内)。//!注意,这里的star和end不是指bit的下标,而是字节(byte)的下标。比如start为1,则实际对应的bit下标为8(1byte=8bit)client.setbit(bitKey,0,‘1’);client.bitcount(bitKey,redis.print);awaitsleep(0.2);console.log(‘—-获取位置—-’);//4.获取第一次出现0或1的位置,获取某个偏移量之后第一次出现0或1的位置client.bitpos(bitKey,0,redis.print)client.bitpos(bitKey,1,redis.print)//1=>82=>16==[8,16]client.bitpos(bitKey,1,2,redis.print)awaitsleep(0.2);console.log(‘—-BITFIELD—-’);//设置value=helloclient.setbit(bitKey,0,‘0’);client.get(bitKey,redis.print);//geti40从0开始取4位即0110,有符号/无符号转十进制为6,1*2^2+1*2^1=6,结果一致constget1Arg=[‘get’,‘i4’,0];//!geti44从4开始取4位即1000,有符号转十进制为-8,1000=>constget2Arg=[‘get’,‘i4’,4];//5.bitfieldclient.bitfield(bitKey,get1Arg,redis.print);client.bitfield(bitKey,get2Arg,redis.print);//6.incrby//2位开始连续4位无符号自增constincrby1Arg=[‘incrby’,‘u4’,2,1];client.bitfield(bitKey,incrby1Arg,redis.print);/*它用来对指定范围的位进行自增操作。既然提到自增,就有可能出现溢出。如果增加了正数,会出现上溢,如果增加的是负数,就会出现下溢出。【Redis默认的处理是折返】。如果出现了溢出,就将溢出的符号位丢掉。如果是8位无符号数255,加1后就会溢出,会全部变零。如果是8位有符号数127,加1后就会溢出变成-128。/awaitsleep(0.2);console.log(‘—-BITOP—-’);};20581419136298509954185548911121429733070491100764827568152669734151278456331661094323490405783745578896867764260898323394695774729635243257987223681446572593138618053240853592667493281798612

返回值

/Reply:OKReply:helloReply:1Reply:21Reply:22—-获取位置—-Reply:3Reply:0Reply:17—-BITFIELD—-Reply:helloReply:6Reply:-8Reply:11—-BITOP—-*/BITFIELD

字母数值二进制(高位<-低位)h10401101000e10101100101l10801101100l10801101100o11101101111

127.0.0.1:6379>setwhelloOK127.0.0.1:6379>bitfieldwgetu40#从w的第0个位开始取4个位(0110),结果为无符号数(u)1)(integer)6127.0.0.1:6379>bitfieldwgetu32#从w的第2个位开始取3个位(101),结果为无符号数(u)1)(integer)5127.0.0.1:6379>bitfieldwgeti40#从w的第0个位开始取4个位(0110),结果为有符号数(i)1)(integer)6127.0.0.1:6379>bitfieldwgeti32#从w的第2个位开始取3个位(101),结果为有符号数(i)1)(integer)-3127.0.0.1:6379>bitfieldwsetu8897#从第9个位开始,将接下来8个位用无符号数97(字母a)替换1)(integer)101127.0.0.1:6379>getwBITOPoperationdestkeykey[key…]

基本操作其实还是用终端比较好,直接贴命令

127.0.0.1:6379>setaa#二进制01100001OK127.0.0.1:6379>setcc#二进制01100011OK127.0.0.1:6379>bitopanddestkeyac#与操作01100001->a(integer)1127.0.0.1:6379>getdestkey“a”

127.0.0.1:6379>setaa#二进制01100001OK127.0.0.1:6379>setbb#二进制01100010OK127.0.0.1:6379>bitopordestkeyab#或操作01100011->c(integer)1127.0.0.1:6379>getdestkey“c”

127.0.0.1:6379>setaa#二进制01100001OK127.0.0.1:6379>setzZ#二进制01011010(大写的Z)OK127.0.0.1:6379>bitopxordestkeyaz#异或00111011->;分号(integer)1127.0.0.1:6379>getdestkey“;”实战前奏

如果对位运算不熟悉的同学,可以先复习一下。

链接放这里了:

理解有符号数和无符号数位运算世界畅游指南

位图实战目标打卡判断某天是否打卡统计某月打卡总次数获取某用户在某月的打卡信息连续打卡的起止时间最长连续天数统计指定区间的打卡次数总的调用

(asyncfunction(){constbitmap=newBitMap();//初始化数据awaitbitmap.initData([‘2020-10’]);//展示10月份全部签到数据awaitbitmap.getAllData([‘2020-10’]);const[uid1,uid2]=[1,2]//用户X签到awaitbitmap.userSign(uid2,‘2020-11-18’);//用户X在XX日期是否签到awaitbitmap.judgeUserSign(uid2,‘2020-11-18’);//用户X在XX月总的签到次数awaitbitmap.getUserSignCount(uid2,‘2020-10’);//用户X在XX月第一次签到的日期awaitbitmap.getFirstSignDate(uid2,‘2020-11’);//用户XX在XX月签到的情况awaitbitmap.getSignInfo(uid2,‘2020-10’);//某个区间内,连续签到的人数总和awaitbitmap.signAllWeek();awaitcommon.sleep(1);process.exit(1);})()66941031572676539127373249176442533131677904680856451429544738361496977131263413555395899898145694842260655722978212395759114068796234988218835667209324705228928718617519152100748843050487330

返回值

?2位图git:(main)?ts-nodesign.ts用户1在2020-10月签到数据:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1用户2在2020-10月签到数据:0,0,1,1,1,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,1,0,1,1,0,1,1,1用户3在2020-10月签到数据:1,0,1,0,0,1,0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1用户4在2020-10月签到数据:1,0,0,1,0,1,1,0,1,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,0,1,1,0,1,1,0用户5在2020-10月签到数据:1,0,1,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,1用户6在2020-10月签到数据:0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,0用户2在2020-11-18签到为1用户1在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0用户2在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0用户3在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0用户4在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0用户5在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0用户6在2020-11月签到数据:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0用户2在2020-11-18签到状态为1用户2在2020-10月份签到总次数为15用户2在2020-11月份首次签到日期为2020-11-18——用户2在2020-10签到情况——-当月签到连续情况为:[{signCount:4,days:[3,4,5,6]},{signCount:3,days:[29,30,31]},{signCount:2,days:[26,27]},{signCount:2,days:[23,24]},{signCount:1,days:[19]},{signCount:1,days:[17]},{signCount:1,days:[13]},{signCount:1,days:[11]}]最长的连续签到次数:4最长的连续签到次数日期为:3,4,5,6———-本月某七天———–本月第4到10天中所有的签到次数:1具体实现

实现部分涉及:

位操作多种实现方法

/*获取某些月份总的签到数据@paramtotalMonth如:[‘2020-10’]/publicasyncgetAllData(totalMonth:string[]){for(letuid=1;uid<=this.allUser;uid++){consttotal=[];//获取上月份的起止时间for(constmonthoftotalMonth){//month对应的天数const{days}=UtilDate.daysInMonth(month);//allUser用户ID作为key中的标示//【偏移量+1】就是某月对应的几号letoffset=0;while(offset<days){constbit=awaitthis.client.getbit(this.genKey({date:month,uid}),offset);total.push(bit);offset++;}constresult=用户${uid}在${month}月签到数据:${total};console.log(result);}}}/用户在某天签到@paramuid用户ID@paramdateYYYY-MM—DD/publicasyncuserSign(uid:number,date:string){constoffset=UtilDate.dayOfNumInMonth(date);conststatus=SIGN.YES;awaitthis.client.setbit(this.genKey({date,uid}),offset-1,status);console.log(用户${uid}在${date}签到为${status});}/*判断用户在某天是否签到@paramuid用户ID@paramdateYYYY-MM—DD*/publicasyncjudgeUserSign(uid:number,date:string){constoffset=UtilDate.dayOfNumInMonth(date);conststatus=awaitthis.client.getbit(this.genKey({date,uid}),offset-1);awaitthis.getAllData([‘2020-11’]);console.log(用户${uid}在${date}签到状态为${status});}/用户X在XX月总的签到次数@paramuid用户ID@paramdateYYYY-MM—DD/publicasyncgetUserSignCount(uid:number,date:string){constcount=awaitthis.client.bitcount(this.genKey({date,uid}));console.log(用户${uid}在${date}月份签到总次数为${count});}35819347873064407897362118149690436889584584235229257323810694313867241625587960756680151399733363379451493450857115982122753486539161002417426120837077429544456889802926769111772825526951967

/***获取当月签到情况*1.当月最长的签到天数2.@paramuid@paramdate/publicasyncgetSignInfo(uid:number,date:string){const{days,dayList}=UtilDate.daysInMonth(date);constkey=this.genKey({date,uid});//days该月总天数constbitValue=awaitthis.genBitIntervalValue({key,start:0,length:days});if(bitValue===-1){console.log(‘相关信息不存在’)return}letsignCount=0;constsignInfo=[];letsignValue=bitValue;//从后往前算for(letindex=dayList.length;index>0;index–){//位运算//先左后右,如果和原数据相等,则标示最低位是0,即,没有签到//从该月最后一天往前算。if(signValue>>1<<1===signValue){if(signCount>0){//记录连续的长度&位置signInfo.push({signCount,index});//重置连续次数signCount=0;}}else{signCount++;}signValue>>=1;}//记录最后的一次连续【高位】if(signCount>0){signInfo.push({signCount,index:0});}//统计连续的天数、连续的日期constresult=[];for(constitemofsignInfo){const{signCount,index}=item;constdays=[];leti=1;let_index=index+1;while(i<=signCount){days.push(_index++);i++;}constarg={signCount,days,}result.push(arg);}//排序函数逆序排列constcompare=(p:any)=>(m:any,n:any)=>-(m[p]-n[p]);result.sort(compare(‘signCount’));console.log(------用户${uid}在${date}签到情况-------)console.log(“当月签到连续情况为:”,‘\n’,result);console.log(最长的连续签到次数:${result[0].signCount});console.log(最长的连续签到次数日期为:${result[0].days});}15522534116364838351220861394917423591757839011102869569355502644335497518095143027063796606739781966739938441659218246146754788406437324962226842892185100878987317129585394968143275770457282

具体的实例代码:https://github.com/simuty/Integration/blob/main/Redis/

本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。