当前位置:

首页 > 编程开发 > redis的bitmap使用实例分析

redis的bitmap使用实例分析

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

1.位图简介如果我们需要记录某一用户在一年中每天是否有登录我们的系统这一需求该如何完成呢?如果使用KV存储,每个用户需要记录365个,当用户量上亿时,这所需要的存储空间是惊人的。Redis为我们提供了位图这一数据结构,每个用户每天的登录记录只占据一位,365天就是365位,仅仅需要46字节就可存储,极大地节约了存储空间。位图数据结构其实并不是一个全新的玩意,我们可以简单的认为就是个数组,只是里面的内容只能为0或1而已(二进制位数组)。2.命令实战Redis提供了SETBIT、GETBIT、BITCOUNT

本文目录

    redis的bitmap使用实例分析

    1.位图简介

    如果我们需要记录某一用户在一年中每天是否有登录我们的系统这一需求该如何完成呢?如果使用KV存储,每个用户需要记录365个,当用户量上亿时,这所需要的存储空间是惊人的。

    Redis 为我们提供了位图这一数据结构,每个用户每天的登录记录只占据一位,365天就是365位,仅仅需要46字节就可存储,极大地节约了存储空间。

    redis的bitmap使用实例分析

    位图数据结构其实并不是一个全新的玩意,我们可以简单的认为就是个数组,只是里面的内容只能为0或1而已(二进制位数组)。

    2.命令实战

    Redis提供了SETBIT、GETBIT、BITCOUNT、BITOP四个常用命令用于处理二进制位数组。

    • SETBIT:为位数组指定偏移量上的二进制位设置值,偏移量从0开始计数,二进制位的值只能为0或1。返回原位置值。

    • GETBIT:获取指定偏移量上二进制位的值。

    • BITCOUNT:统计位数组中值为1的二进制位数量。

    • BITOP:对多个位数组进行按位与、或、异或运算。

    127.0.0.1:6379> SETBIT first 0 1    # 0000 0001
    (integer) 0
    127.0.0.1:6379> SETBIT first 3 1    # 0000 1001
    (integer) 0
    127.0.0.1:6379> SETBIT first 0 0    # 0000 1000
    (integer) 1
    
    127.0.0.1:6379> GETBIT first 0
    (integer) 0
    127.0.0.1:6379> GETBIT first 3
    (integer) 1
    
    127.0.0.1:6379> BITCOUNT first      # 0000 1000
    (integer) 1
    127.0.0.1:6379> SETBIT first 0 1    # 0000 1001
    (integer) 0
    127.0.0.1:6379> BITCOUNT first      # 0000 1001
    (integer) 2
    127.0.0.1:6379> SETBIT first 1 1    # 0000 1011
    (integer) 0
    127.0.0.1:6379> BITCOUNT first      # 0000 1011
    (integer) 3
    
    127.0.0.1:6379> SETBIT x 3 1        
    (integer) 0
    127.0.0.1:6379> SETBIT x 1 1        
    (integer) 0
    127.0.0.1:6379> SETBIT x 0 1        # 0000 1011
    (integer) 0
    127.0.0.1:6379> SETBIT y 2 1        
    (integer) 0
    127.0.0.1:6379> SETBIT y 1 1        # 0000 0110
    (integer) 0
    127.0.0.1:6379> SETBIT z 2 1        
    (integer) 0
    127.0.0.1:6379> SETBIT z 0 1        # 0000 0101
    (integer) 0
    
    127.0.0.1:6379> BITOP AND andRes x y z    #0000 0000
    (integer) 1
    127.0.0.1:6379> BITOP OR orRes x y z      #0000 1111
    (integer) 1
    127.0.0.1:6379> BITOP XOR x y z           #0000 1000
    (integer) 1
    
    # 对给定的位数组进行按位取反
    127.0.0.1:6379> SETBIT value 0 1
    (integer) 0
    127.0.0.1:6379> SETBIT value 3 1            #0000 1001
    (integer) 0
    127.0.0.1:6379> BITOP NOT notValue value    #1111 0110
    (integer) 1

    3.BitMap源码分析

    3.1 数据结构

    如下展示了一个用 SDS 表示的一字节(8位)长的位图:

    redis的bitmap使用实例分析

    扩展:Redis 中的每个对象都是有一个 redisObject 结构表示的。

    typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 执行底层实现的数据结构的指针
    void *ptr;
    } robj;
    • type 的值为 REDIS_STRING表示这是一个字符串对象

    • sdshdr.len 的值为1表示这个SDS保存了一个1字节大小的位数组

    • buf数组中的buf[0]实际保存了位数组

    • buf数组中的buf[1]为自动追加的\0字符

    为了便于我们观察,buf数组的每个字节都用一行来表示,buf[i]表示这是buf数组的第i个字节,buf[i]之后的8个格子表示这个字节上的8位。

    为再次拉齐各位思想,如下展示了另外一个位数组:

    redis的bitmap使用实例分析

    位数组由buf[0]、buf[1]和buf[2]三个字节保存,真实数据为1111 0000 1100 0011 1010 0101

    3.2 GETBIT

    GETBIT用于返回位数组在偏移量上的二进制位的值。值得我们注意的是,GETBIT的时间复杂度是O(1)。

    GETBIT命令的执行过程如下:

    1. 计算$ byte = \lfloor offset\p8 \rfloor $ (即>>3),byte 值表示指定的                             o                         f                         f                         s                         e                         t                            offset                 offset 位于位数组的哪个字节(计算在第几行);

    2. 指定                             b                         u                         f                         [                         i                         ]                            buf[i]                 buf[i] 中的                             i                            i                 i了,接下来就要计算在8个字节中的第几位呢?使用 $ bit = (offset\ %\ 8)+1 $计算可得;

    3. 根据                             b                         y                         t                         e                            byte                 byte 和                             b                         i                         t                            bit                 bit 在位数组中定位到目标值返回即可。

    GETBIT命令源码如下所示:

    void getbitCommand(client *c) {
        robj *o;
        char llbuf[32];
        uint64_t bitoffset;
        size_t byte, bit;
        size_t bitval = 0;
        // 获取offset
        if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
            return;
        // 查找对应的位图对象
        if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
            checkType(c,o,OBJ_STRING)) return;
    		// 计算offset位于位数组的哪一行
        byte = bitoffset >> 3;
        // 计算offset在一行中的第几位,等同于取模
        bit = 7 - (bitoffset & 0x7);
        // #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
        if (sdsEncodedObject(o)) {
            // SDS 是RAW 或者 EMBSTR类型
            if (byte < sdslen(o->ptr))
                // 获取指定位置的值
                // 注意它不是真正的一个二维数组不能用((uint8_t*)o->ptr)[byte][bit]去获取呀~
                bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
        } else {
            //  SDS 是 REDIS_ENCODING_INT 类型的整数,先转为String
            if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
                bitval = llbuf[byte] & (1 << bit);
        }
    
        addReply(c, bitval ? shared.cone : shared.czero);
    }

    redis的bitmap使用实例分析

    举个栗子

    以GETBIT array 3为例,array表示上图中三个字节的位数组。

    1. $byte = \lfloor 3 \p8 \rfloor$ 得到值为0,说明在 $buf[0]$ 上
    2. $bit = (3\ mod\ 8 ) + 1$得到值为4
    3. 定位到 $buf[0]$ 字节的从左至右第4个位置上

    redis的bitmap使用实例分析

    因为 GETBIT 命令执行的所有操作都可以在常数时间内完成,所以该命令的算法复杂度为O(1)。

    3.3 SETBIT

    SETBIT用于将位数组在偏移量的二进制位的值设为value,并向客户端返回旧值。

    SITBIT命令的执行过程如下:

    1. 计算                             l                         e                         n                         =                         ⌊                         o                         f                         f                         s                         e                         t                         ÷                         8                         ⌋                         +                         1                            len = \lfloor offset÷8\rfloor + 1                 len=⌊offset÷8⌋+1,                             l                         e                         n                            len                 len值记录了保存                             o                         f                         f                         s                         e                         t                            offset                 offset偏移量指定的二进制位至少需要多少字节

    2. 检查位数组的长度是否小于                             l                         e                         n                            len                 len,如果是的话,将SDS的长度扩展为len字节,并将所有新扩展空间的二进制位设置为0

    3. 计算                             b                         y                         t                         e                         =                         ⌊                         o                         f                         f                         s                         e                         t                         ÷                         8                         ⌋                            byte = \lfloor offset÷8\rfloor                 byte=⌊offset÷8⌋,                             b                         y                         t                         e                            byte                 byte值表示指定的                             o                         f                         f                         s                         e                         t                            offset                 offset位于位数组的那个字节(就是计算在那个                             b                         u                         f                         [                         i                         ]                            buf[i]                 buf[i]中的                             i                            i                 i)

    4. 使用                             b                         i                         t                         =                         (                         o                         f                         f                         s                         e                         t                                                   m                         o                         d                                                   8                         )                         +                         1                            bit = (offset\ mod\ 8)+1                 bit=(offset mod 8)+1计算可得目标                             b                         u                         f                         [                         i                         ]                            buf[i]                 buf[i]的具体第几位

    5. 根据                             b                         y                         t                         e                            byte                 byte和                             b                         i                         t                            bit                 bit的值,首先保存                             o                         l                         d                         V                         a                         l                         u                         e                            oldValue                 oldValue,然后将新值                             v                         a                         l                         u                         e                            value                 value设置到目标位上

    6. 返回旧值

    因为SETBIT命令执行的所有操作都可以在常数时间内完成,所以该命令的算法复杂度为O(1)。

    SETBIT命令源码如下所示:

    void setbitCommand(client *c) {
        robj *o;
        char *err = "bit is not an integer or out of range";
        uint64_t bitoffset;
        ssize_t byte, bit;
        int byteval, bitval;
        long on;
        // 获取offset
        if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
            return;
        // 获取我们需要设置的值
        if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != C_OK)
            return;
    
        /* 判断指定值是否为0或1 */
        if (on & ~1) {
            // 设置了0和1之外的值,直接报错
            addReplyError(c,err);
            return;
        }
        // 根据key查询SDS对象(会自动扩容)
        if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
    
        /* 获得当前值 */
        byte = bitoffset >> 3;
        byteval = ((uint8_t*)o->ptr)[byte];
        bit = 7 - (bitoffset & 0x7);
        bitval = byteval & (1 << bit);
    
        /* 更新值并返回旧值 */
        byteval &= ~(1 << bit);
        byteval |= ((on & 0x1) << bit);
        ((uint8_t*)o->ptr)[byte] = byteval;
        // 发送数据修改通知
        signalModifiedKey(c,c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
        server.dirty++;
        addReply(c, bitval ? shared.cone : shared.czero);
    }

    redis的bitmap使用实例分析

    举个栗子1.0

    array表示上图中的三个字节位数组。以SETBIT array 10 1为例:

    1. $ len = \lfloor10÷8\rfloor + 1$得到值为2,说明至少需要2字节长的位数组

    2. 检查是否需要扩容,不需要

    3. 计算$byte = 1                             ,                         计                         算                            ,计算                 ,计算bit = 3                             ,                         保                         存                            ,保存                 ,保存oldValue$,设置新值

    4. 返回                             o                         l                         d                         V                         a                         l                         u                         e                            oldValue                 oldValue

    redis的bitmap使用实例分析

    举个栗子2.0

    假设目标位数组为1字节长度。执行SETBIT array 12 1,执行如下:

    1.                             l                         e                         n                         =                         ⌊                         12                         ÷                         8                         ⌋                         +                         1                            len = ⌊12÷8⌋ + 1                 len=⌊12÷8⌋+1 得到值为2,说明需要2字节长的 SDS

    2. 检查是否需要扩容,需要呀!由 SDS 的自动扩容机制可知, SDS 将按新长度的两倍扩容。

    3. 计算 $byte = 1 $

    4. 计算 $bit = 5 $

    5. 保存                             o                         l                         d                         V                         a                         l                         u                         e                            oldValue                 oldValue,设置新值

    6. 返回                             o                         l                         d                         V                         a                         l                         u                         e                            oldValue                 oldValue

    redis的bitmap使用实例分析

    3.4 BITCOUNT

    BITCOUNT命令用于统计给定位数组中值为1的二进制位的数量。功能似乎不复杂,但实际上要高效地实现这个命令并不容易,需要用到一些精巧的算法。

    统计一个位数组中非0二进制位的数量在数学上被称为"计算汉明重量"。

    3.4.1 暴力遍历

    实现BITCOUNT命令最简单直接的方法,就是遍历位数组中的每个二进制位,并在遇到值为1的二进制位时将计数器加1。

    小数据量还好,大数据量直接PASS!

    3.4.2 查表法

    对于一个有限集合来说,集合元素的排列方式是有限的,并且对于一个有限长度的位数组来说,它能表示的二进制位排列也是有限的。根据这个原理,我们可以创建一个表,表的键为某种排列的位数组,而表的值则是相应位数组中值为1的二进制位的数量。

    对于8位长的位数组来说,我们可以创建下表,通过这个表格我们可以一次从位数组中读入8位,然后根据这8位的值进行查表,直接知道这个值包含了多少个1。

    redis的bitmap使用实例分析

    可惜,查表法耗内存呀!

    3.4.3 二进制位统计算法:variable-precision SWAR

    目前已知效率最好的通用算法为variable-precision SWAR算法,该算法通过一系列位移和位运算操作,可以在常数时间(这就很牛逼了)内计算多个字节的汉明重量,并且不需要使用任何额外的内存。

    SWAR算法代码如下所示:

    uint32_t swar(uint32_t i) {
        // 5的二进制:0101
        i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
        // 3的二进制:0011
        i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
        i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);
        i = (i*(0x01010101) >> 24);
        return i;
      
        i = i - ((i >> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
        return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
    }

    下面描述一下这几步都干了啥:

    1. 步骤一计算出的值i的二进制表示可以按每两个二进制位为一组进行分组,各组的十进制表示就是该组的1的数量;

    2. 步骤二计算出的值i的二进制表示可以按每四个二进制位为一组进行分组,各组的十进制表示就是该组的1的数量;

    3. 步骤三计算出的值i的二进制表示可以按每八个二进制位为一组进行分组,各组的十进制表示就是该组的1的数量;

    4. 步骤四的i*0x01010101语句计算出bitarray中1的数量并记录在二进制位的最高八位,而>>24语句则通过右移运算,将bitarray的汉明重量移动到最低八位,得出的结果就是bitarray的汉明重量。

    举个栗子

    对于调用swar(0xFBB4080B),步骤一将计算出值0xA6640406,这个值表的每两个二进制位的十进制表示记录了0xFBB4080B每两个二进制位的汉明重量。

    redis的bitmap使用实例分析

    步骤二将计算出值0x43310103,这个值表的每四个二进制位的十进制表示记录了0xFBB4080B每四个二进制位的汉明重量。

    redis的bitmap使用实例分析

    步骤三将计算出值0x7040103,这个值表的每八个二进制位的十进制表示记录了0xFBB4080B每八个二进制位的汉明重量。

    redis的bitmap使用实例分析

    步骤四首先计算0x7040103 * 0x01010101 = 0xF080403,将汉明重量聚集到二进制位的最高八位。

    redis的bitmap使用实例分析

    之后计算0xF080403 >> 24,将汉明重量移动到低八位,得到最终值0x1111,即十进制15。

    如果您是Java程序员,可以去看看Integer.bitCount方法,也是基于SWAR算法的思想哦!

    大家也可以看看StackFlow上大神对它的讲解:[How does this algorithm to count the number of set bits in a 32-bit integer work?](https://stackoverflow.com/questions/22081738/how-does-this-algorithm-to-count-the-number-of-set-bits-in-a-32-bit-integer-work)3.4.4 源码分析

    Redis 中通过调用redisPopcount方法统计汉明重量,源码如下所示:

    long long redisPopcount(void *s, long count) {
        long long bits = 0;
        unsigned char *p = s;
        uint32_t *p4;
        // 为查表法准备的表
        static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
        // CPU一次性读取8个字节,如果4字节跨了两个8字节,需要读取两次才行
        // 所以考虑4字节对齐,只需读取一次就可以读取完毕
        while((unsigned long)p & 3 && count) {
            bits += bitsinbyte[*p++];
            count--;
        }
    
        // 一次性处理28字节,单独看一个aux就容易理解了,其实就是SWAR算法
        // uint32_t:4字节
        p4 = (uint32_t*)p;
        while(count>=28) {
            uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
    
            aux1 = *p4++;// 一次性读取4字节
            aux2 = *p4++;
            aux3 = *p4++;
            aux4 = *p4++;
            aux5 = *p4++;
            aux6 = *p4++;
            aux7 = *p4++;
            count -= 28;// 共处理了4*7=28个字节,所以count需要减去28
    
            aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
            aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
            aux2 = aux2 - ((aux2 >> 1) & 0x55555555);
            aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);
            aux3 = aux3 - ((aux3 >> 1) & 0x55555555);
            aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
            aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
            aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
            aux5 = aux5 - ((aux5 >> 1) & 0x55555555);
            aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333);
            aux6 = aux6 - ((aux6 >> 1) & 0x55555555);
            aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333);
            aux7 = aux7 - ((aux7 >> 1) & 0x55555555);
            aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333);
            bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) +
                        ((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) +
                        ((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) +
                        ((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) +
                        ((aux5 + (aux5 >> 4)) & 0x0F0F0F0F) +
                        ((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
                        ((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
        }
        /* 剩余的不足28字节,使用查表法统计 */
        p = (unsigned char*)p4;
        while(count--) bits += bitsinbyte[*p++];
        return bits;
    }

    不难发现 Redis 中同时运用了查表法和SWAR算法完成BITCOUNT功能。

    4.面试题:40亿QQ号去重

    如果没有1GB的内存限制,我们可以使用排序和Set完成这个算法:

    • 排序:① 首先将40亿个QQ号进行排序;② 从小到大遍历,跳过重复元素只取第一个元素。

    • Set:将40亿个QQ号统统放进Set集合中,自动完成去重,Perfect

    这样回答是要GG的节奏呀!

    对40亿个QQ号进行排序需要多少时间?这个存储了40亿QQ号的数组容量已经超过1GB了,同理Set集合存储这么多的QQ号也导致内存超限了。

    BitMap去重

    **这不巧了么~我们可以使用刚刚学的BITMAP来去重呀!**一个字节可以记录8个数是否存在(类似于计数排序),将QQ号对应的offset的值设置为1表示此数存在,遍历完40亿个QQ号后直接统计BITMAP上值为1的offset即可完成QQ号的去重。

    如果是对40亿个QQ号进行排序也是可以用位图完成的哦~一样的思想

    5.位图实战

    既然我们深入了解了BITMAP,那不进行个实战项目可说不过去呀!

    我们使用BITMAP实现GITHUB中统计每天提交次数的这个小功能,基于SpringBoot+Echarts实现

    redis的bitmap使用实例分析

    如果是记录登录状态我们可以很方便的使用0和1记录,如果是记录提交次数就显得BITMAP无用了,没关系,我们可以使用一个字节来记录提交次数,只是在业务上需要处理好十进制和二进制直接的转换而已。

    生成模拟数据
    public void genTestData() {
        if(redisUtils.isExist(CommonConstant.KEY)){
            return;
        }
        // 获取当前年的总天数
        int days = getDays();
        for (int i = 0; i < days; i++) {
            int random = ThreadLocalRandom.current().nextInt(64);
            // 生成随机数表示每天的PR次数
            String binaryString = Integer.toBinaryString(random);
            if (binaryString.length() < 8) {
                // 填充0
                if(binaryString.length() == 0){binaryString = "00000000";}
                else if(binaryString.length() == 1){binaryString = "0000000"+binaryString;}
                else if(binaryString.length() == 2){binaryString = "000000"+binaryString;}
                else if(binaryString.length() == 3){binaryString = "00000"+binaryString;}
                else if(binaryString.length() == 4){binaryString = "0000"+binaryString;}
                else if(binaryString.length() == 5){binaryString = "000"+binaryString;}
                else if(binaryString.length() == 6){binaryString = "00"+binaryString;}
                else if(binaryString.length() == 7){binaryString = "0"+binaryString;}
            }
            char[] chars = binaryString.toCharArray();
            for (int j = 0; j < chars.length; j++) {
                // 设置BitMap
                redisUtils.setBit(CommonConstant.KEY,i*8+j,chars[j]);
            }
        }}/**
     * 获取当前年的总天数
     * @return days 总天数
     */private int getDays(){
        Calendar calOne = Calendar.getInstance();
        int year = calOne.get(Calendar.YEAR);
        System.out.println(year);
        Calendar calTwo = new GregorianCalendar(year, 11, 31);
        return calTwo.get(Calendar.DAY_OF_YEAR);}
    获取数据
    public List getPushData() {
        List res = new ArrayList<>(366);
        // 没有数据就先造数据
        genTestData();
        int days = getDays();
        for(long i=0;i

    这里觉得可以直接将所有的bit统统返回,在前端进行分割处理

    前端渲染
    
        var chartDom = document.getElementById('main');
        var myChart = echarts.init(chartDom);
        var option;
    
        function getVirtulData(year) {
            var date = +echarts.number.parseDate(year + '-01-01');
            var end = +echarts.number.parseDate(+year + 1 + '-01-01');
            var dayTime = 3600 * 24 * 1000;
            var data = [];
            $.ajax({
                "url":'http://localhost:8080/test/getPushData',
                "async":false, // ajax同步获取
                success:function (res){
                    for (let time = date,k=0; time < end && k < res.data.length; time += dayTime,k++) {
                        data.push([
                            echarts.format.formatTime('yyyy-MM-dd', time),
                            parseInt(res.data[k],2)//客户端完成进制转换,不放在服务端完成
                        ]);
                    }
                }
            })
    
            return data;
        }
        option = {
            title: {
                top: 30,
                left: 'left',
                text: 'BitMap Demo'
            },
            tooltip: {},
            visualMap: {
                min: 0,
                max: 32,
                type: 'piecewise',
                orient: 'horizontal',
                left: 'right',
                top: 220,
                pieces: [
                    {min: 0, max: 0,label:"less"},
                    {min: 1, max: 10,label:" "},
                    {min: 1, max: 20,label:" "},
                    {min: 21, max: 40,label:" "},
                    {min: 41, max: 64,label:"more"},
                ],
                inRange: {
                    color: [ '#EAEDF0', '#9AE9A8', '#41C363', '#31A14E', '#206D38' ],//颜色设置 
                    colorAlpha: 0.9,//透明度
                }
            },
            calendar: {
                top: 120,
                left: 30,
                right: 30,
                cellSize: 13,
                range: '2022',
                splitLine: { show: false },//不展示边线
                itemStyle: {
                    borderWidth: 0.5
                },
                yearLabel: { show: false }
            },
            series: {
                type: 'heatmap',
                coordinateSystem: 'calendar',
                data: getVirtulData('2022')
            }
        };
    
        option && myChart.setOption(option);
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    php72w redis,docker php7安装php-redis
    php72w redis,docker php7安装php-redis

    在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

    php 如何配置 redis,php 如何设置redis
    php 如何配置 redis,php 如何设置redis

    配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

    C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

    如何在Golang架构中部署Redict作为高能数据临时存储中转站
    如何在Golang架构中部署Redict作为高能数据临时存储中转站

    Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

    在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

    在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

    先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

    高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

    Golang 实现基于 Redis 的延时任务队列
    Golang 实现基于 Redis 的延时任务队列

    基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

    操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?