位图存储空间计算公式,存储空间的计算公式

首页 > 实用技巧 > 作者:YD1662023-11-11 21:49:47

前言
  1. 如果要统计一篇文章的阅读量,可以直接使用 Redis 的 incr 指令来完成
  2. 如果要求阅读量必须按用户去重,那就可以使用 set 来记录阅读了这篇文章的所有用户 id,获取 set 集合的长度就是去重阅读量。但是如果爆款文章阅读量太大,set 会浪费太多存储空间。
  3. 这时候我们就要使用 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-》1 client.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=hello client.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.bitfield client.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----'); }; 20 58 14 19 13 62 98 50 99 54 18 55 48 91 11 21 42 97 3 30 70 49 1 100 7 64 82 75 68 15 26 69 73 41 51 27 84 56 33 16 6 10 94 32 34 90 40 5 78 37 45 57 88 96 8 67 76 4 2 60 89 83 23 39 46 95 77 47 29 63 52 43 25 79 87 22 36 81 44 65 72 59 31 38 61 80 53 24 0 85 35 92 66 74 93 28 17 9 86 12

返回值

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

字母数值二进制(高位<-低位)h1040110 1000e1010110 0101l1080110 1100l1080110 1100o1110110 1111

127.0.0.1:6379>setwhello OK 127.0.0.1:6379>bitfieldwgetu40#从w的第0个位开始取4个位(0110),结果为无符号数(u) 1)(integer)6 127.0.0.1:6379>bitfieldwgetu32#从w的第2个位开始取3个位(101),结果为无符号数(u) 1)(integer)5 127.0.0.1:6379>bitfieldwgeti40#从w的第0个位开始取4个位(0110),结果为有符号数(i) 1)(integer)6 127.0.0.1:6379>bitfieldwgeti32#从w的第2个位开始取3个位(101),结果为有符号数(i) 1)(integer)-3 127.0.0.1:6379>bitfieldwsetu8897#从第9个位开始,将接下来8个位用无符号数97(字母a)替换 1)(integer)101 127.0.0.1:6379>getw BITOP operation destkey key [key …]

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

127.0.0.1:6379>setaa#二进制01100001 OK 127.0.0.1:6379>setcc#二进制01100011 OK 127.0.0.1:6379>bitopanddestkeyac#与操作01100001->a (integer)1 127.0.0.1:6379>getdestkey"a"

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

127.0.0.1:6379>setaa#二进制01100001 OK 127.0.0.1:6379>setzZ#二进制01011010(大写的Z) OK 127.0.0.1:6379>bitopxordestkeyaz#异或00111011->;分号 (integer)1 127.0.0.1:6379>getdestkey ";" 实战前奏

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

链接放这里了:

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

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

(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); })() 66 94 10 31 57 26 76 53 91 27 37 32 49 17 6 44 25 33 13 16 77 90 46 80 85 64 51 42 9 54 47 38 36 14 96 97 71 3 12 63 41 35 55 39 58 99 89 81 45 69 4 84 22 60 65 5 72 29 78 21 23 95 7 59 11 40 68 79 62 34 98 82 18 83 56 67 20 93 24 70 52 28 92 87 1 8 61 75 19 15 2 100 74 88 43 0 50 48 73 30

返回值

➜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 具体实现

实现部分涉及:

  1. 位操作
  2. 多种实现方法

/** *获取某些月份总的签到数据 *@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}`); } 35 81 93 47 87 30 64 40 78 97 36 21 18 14 96 90 43 68 89 58 45 84 23 5 22 92 57 32 38 10 69 4 31 3 86 72 41 62 55 8 79 60 75 66 80 15 13 99 73 33 63 37 94 51 49 34 50 85 71 1 59 82 12 27 53 48 65 39 16 100 24 17 42 61 20 83 70 7 74 29 54 44 56 88 98 0 2 9 26 76 91 11 77 28 25 52 6 95 19 67

/** *获取当月签到情况 *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}`); } 15 52 25 34 1 16 36 48 38 35 12 20 86 13 94 91 74 23 59 17 57 83 90 11 10 28 69 56 93 55 50 26 44 33 54 97 51 80 95 14 30 2 70 63 79 6 60 67 39 78 19 66 73 99 3 84 41 65 92 18 24 61 46 75 47 88 40 64 37 32 49 62 22 68 42 89 21 85 100 87 8 98 7 31 71 29 58 53 9 4 96 81 43 27 5 77 0 45 72 82

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

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.