目錄
#1.點陣圖簡介
2.指令實戰
3.BitMap原始碼分析
3.1 資料結構
举个栗子
3.3 SETBIT
舉個栗子1.0
實作
對於一個有限集合來說,集合元素的排列方式是有限的,對於一個有限長度的位數組來說,它能表示的二進位位排列也是有限的。根據這個原理,我們可以建立一個表,表的鍵為某種排列的位數組,而表的值則是對應位數組中值為1的二進位位元的數量。
3.4.3 二进制位统计算法:variable-precision SWAR
4.面试题:40亿QQ号去重
BitMap去重
5.位图实战
生成模拟数据
获取数据
前端渲染
首頁 資料庫 Redis 一文搞懂redis的bitmap

一文搞懂redis的bitmap

Apr 27, 2022 pm 07:48 PM
redis

這篇文章為大家帶來了關於Redis的相關知識,其中主要介紹了bitmap問題,Redis 為我們提供了點陣圖這個資料結構,點陣圖資料結構其實不是一個全新的玩意,我們可以簡單的認為就是個數組,只是裡面的內容只能為0或1而已,希望對大家有幫助。

一文搞懂redis的bitmap

推薦學習:Redis影片教學

#1.點陣圖簡介

#如果我們需要記錄某一使用者在一年中每天是否有登入我們的系統這項需求該如何完成呢?如果使用KV存儲,每個用戶需要記錄365個,當用戶量上億時,這所需的存儲空間是驚人的。

Redis 為我們提供了位圖這一資料結構,每個使用者每天的登入記錄只佔據一位,365天就是365位,僅需要46字節就可存儲,極大節約了儲存空間。

位圖資料結構其實不是一個全新的玩意,我們可以簡單的認為就是個數組,只是裡面的內容只能為0或1而已(二進制位數組)。

2.指令實戰

Redis提供了SETBITGETBITBITCOUNTBITOP四個常用指令用於處理二進位位元組。

  • 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 中的每個物件都是由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位元。

為再次拉齊各位思想,如下展示了另外一個位數組:

位元組由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             offse t 位於位元組的哪個位元組(計算在第幾個行);
  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);
}
登入後複製

举个栗子

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

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

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

3.3 SETBIT

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

SITBIT命令的执行过程如下:

  1. 計算 我 e n = ⌊ 哦 F F s e t ÷ 8 ⌋ 1 len = \l地板偏移量÷8\r地板 1 len= #⌊offs et÷#8 ##1##1 #,# 我 e n 倫 le###n################值記錄了保存######### 哦 F F s e t 抵銷 ###offset偏移指定的二進位位元至少需要多少位元組
  2. #檢查位元組的長度是否小於                                 l                         e                         n                            len             len#,如果是的話,將SDS的長度擴展為len字節,並將所有新擴展空間的二進位位設置為0
  3. #計算# 乙 y t e = ⌊ 哦 F F s e t ÷ 8 ⌋ 位元組 = \lfloor 偏移÷8\rfloor 位元組 =offset÷8 乙 y t e 位元組 位元組 值表示指定的 哦 F F s e t 抵銷 #############fset#位於位元組的那個位元組(就是計算在那個#                                 b                         u                         f                         [                         i                         ]                            buf[i]             buf[i]中的                                 i                            i                 i#)
  4. ##使用
  5. 乙 我 t = ( 哦 F F s e t 米 哦 d 8 ) 1 位元 = (偏移\ mod\ 8) 1 bi#t##= (o#ff##set mo##d 8)##1 計算可得目標## 乙 你 F [ 我 ] 緩衝區[i] buf[i]的具體第幾位
  6. 根据 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设置到目标位上
  7. 返回旧值

因为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);
}
登入後複製

舉個栗子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             ol#dV##alue

舉個栗子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             ol#dV##alue#,設定新值
  6. 返回
  7. #                                 o                         l                         d                         V                         a                         l                         u                         e                            oldValue             ol#dV##alue

3.4 BITCOUNT

BITCOUNT

指令用於統計給定位數組中值為1的二進位位元的數量。功能似乎不複雜,但實際上要有效率地實作這個指令並不容易,需要用到一些精巧的演算法。

統計一個位元組中非0二進位位元的數量在數學上被稱為"計算漢明重量"。

3.4.1 暴力遍歷

實作

BITCOUNT

指令最簡單直接的方法,就是遍歷位數群組中的每個二進位位,並在遇到值為1的二進位位元時將計數器加1。

小數據量還好,大數據量直接PASS!

#3.4.2 查表法

對於一個有限集合來說,集合元素的排列方式是有限的,對於一個有限長度的位數組來說,它能表示的二進位位排列也是有限的。根據這個原理,我們可以建立一個表,表的鍵為某種排列的位數組,而表的值則是對應位數組中值為1的二進位位元的數量。

對於8位長的位數組來說,我們可以建立下表,透過這個表格我們可以一次從位數組中讀入8位,然後根據這8位的值進行查表,直接知道這個值包含了多少個1。

可惜,查表法耗記憶體呀!

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每两个二进制位的汉明重量。

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

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

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

之后计算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实现

如果是记录登录状态我们可以很方便的使用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<String> getPushData() {
    List<String> res = new ArrayList<>(366);
    // 没有数据就先造数据
    genTestData();
    int days = getDays();
    for(long i=0;i<days;i++){
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < 8; j++) {
            String bit = redisUtils.getBit(CommonConstant.KEY, i * 8 + j);
            sb.append(bit);
        }
        // 直接返回二进制串,前端转换为十进制
        res.add(sb.toString());
    }
    return res;}
登入後複製

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

前端渲染

<script type="text/javascript">
    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(&#39;yyyy-MM-dd&#39;, time),
                        parseInt(res.data[k],2)//客户端完成进制转换,不放在服务端完成
                    ]);
                }
            }
        })

        return data;
    }
    option = {
        title: {
            top: 30,
            left: &#39;left&#39;,
            text: &#39;BitMap Demo&#39;
        },
        tooltip: {},
        visualMap: {
            min: 0,
            max: 32,
            type: &#39;piecewise&#39;,
            orient: &#39;horizontal&#39;,
            left: &#39;right&#39;,
            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: [ &#39;#EAEDF0&#39;, &#39;#9AE9A8&#39;, &#39;#41C363&#39;, &#39;#31A14E&#39;, &#39;#206D38&#39; ],//颜色设置 
                colorAlpha: 0.9,//透明度
            }
        },
        calendar: {
            top: 120,
            left: 30,
            right: 30,
            cellSize: 13,
            range: &#39;2022&#39;,
            splitLine: { show: false },//不展示边线
            itemStyle: {
                borderWidth: 0.5
            },
            yearLabel: { show: false }
        },
        series: {
            type: &#39;heatmap&#39;,
            coordinateSystem: &#39;calendar&#39;,
            data: getVirtulData(&#39;2022&#39;)
        }
    };

    option && myChart.setOption(option);</script>
登入後複製

推荐学习:Redis视频教程

以上是一文搞懂redis的bitmap的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

redis集群模式怎麼搭建 redis集群模式怎麼搭建 Apr 10, 2025 pm 10:15 PM

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

redis底層怎麼實現 redis底層怎麼實現 Apr 10, 2025 pm 07:21 PM

Redis 使用哈希表存儲數據,支持字符串、列表、哈希表、集合和有序集合等數據結構。 Redis 通過快照 (RDB) 和追加只寫 (AOF) 機制持久化數據。 Redis 使用主從復制來提高數據可用性。 Redis 使用單線程事件循環處理連接和命令,保證數據原子性和一致性。 Redis 為鍵設置過期時間,並使用 lazy 刪除機制刪除過期鍵。

redis查詢的key怎麼唯一 redis查詢的key怎麼唯一 Apr 10, 2025 pm 07:03 PM

Redis採用五種策略確保鍵的唯一性:1. 名稱空間分隔;2. HASH數據結構;3. SET數據結構;4. 字符串鍵的特殊字符;5. Lua腳本驗證。具體策略的選擇取決於數據組織、性能和擴展性需求。

redis怎麼查看所有的key redis怎麼查看所有的key Apr 10, 2025 pm 07:15 PM

要查看 Redis 中的所有鍵,共有三種方法:使用 KEYS 命令返回所有匹配指定模式的鍵;使用 SCAN 命令迭代鍵並返回一組鍵;使用 INFO 命令獲取鍵的總數。

redis集群是如何實現的 redis集群是如何實現的 Apr 10, 2025 pm 05:27 PM

Redis集群是一種分佈式部署模式,允許水平擴展Redis實例,通過節點間通信、哈希槽劃分鍵空間、節點選舉、主從復制和命令重定向來實現:節點間通信:通過集群總線實現虛擬網絡通信。哈希槽:將鍵空間劃分為哈希槽,確定負責鍵的節點。節點選舉:至少需要三個主節點,通過選舉機制確保僅有一個活動主節點。主從復制:主節點負責寫請求,從節點負責讀請求和數據複製。命令重定向:客戶端連接到負責鍵的節點,節點重定向不正確的請求。故障處理:故障檢測、標記下線和重新

redis如何查看版本號 redis如何查看版本號 Apr 10, 2025 pm 05:57 PM

要查看 Redis 版本號,可以使用以下三種方法:(1) 輸入 INFO 命令,(2) 使用 --version 選項啟動服務器,(3) 查看配置文件。

redis-server找不到怎麼辦 redis-server找不到怎麼辦 Apr 10, 2025 pm 06:54 PM

解決redis-server找不到問題的步驟:檢查安裝,確保已正確安裝Redis;設置環境變量REDIS_HOST和REDIS_PORT;啟動Redis服務器redis-server;檢查服務器是否運行redis-cli ping。

redis命令行怎麼用 redis命令行怎麼用 Apr 10, 2025 pm 10:18 PM

使用 Redis 命令行工具 (redis-cli) 可通過以下步驟管理和操作 Redis:連接到服務器,指定地址和端口。使用命令名稱和參數向服務器發送命令。使用 HELP 命令查看特定命令的幫助信息。使用 QUIT 命令退出命令行工具。

See all articles