在簽到統計場景中,可使用 bitmap 數據類型高效的存儲簽到數據,但 getbit 命令只能獲取某一位值,就沒法最優的知足部分業務場景了。php
好比咱們按年去存儲一個用戶的簽到狀況,365 天,只須要 365 / 8 ≈ 46 Byte,1KW 用戶量一年也只須要 44 MB 就足夠了。redis
setbit sign:uid:year 0 1 #第1天 setbit sign:uid:year 1 1 #第2天 ... setbit sign:uid:year 364 1 #第365天
但若是我想獲取某個用戶一年的簽到統計,使用 bitget 命令的話...要循環讀取 365 次,這是沒辦法接受的。編程
若是能一次讀取到以字符串安全
"1000100010100100...001"
的形式表示的位狀態數據,就很好作後續的處理了。網絡
bitmap 其實也是一種特殊的字符串數據,使用 get 命令是能夠讀取出來的,可是以 16 進制的流數據返回的,這裏就涉及到網絡編程中數據傳輸的打包/解包的知識,redis 使用 get 命令讀取 bitmap 數據時,將二進制數據打包成了 16 進制返回給咱們,因此咱們要對此數據包以 16 進制解包,而後轉爲二進制字符串。給出轉換方法:ui
<?php // 第1天的簽到 $redis->setBit('sign:uid:year', 0, 1); // 第234天的簽到 $redis->setBit('sign:uid:year', 233, 1); // 第365天的簽到 $redis->setBit('sign:uid:year', 364, 1); // 使用 get 命令一次性讀取用戶的 bitmap 簽到數據 $bitmap_str = $redis->get("sign:uid:year"); // 對數據流使用網絡字節序(大端)解包拿到16進制數據的字符串形式 $hex_str = unpack("H*", $bitmap_str)[1]; // hex str 的長度 $hex_str_len = strlen($hex_str); // 爲了防止 hex to dec 時發生溢出 // 咱們須要切分 hex str,使得每一份 hex str to dec 時都能落在 int 類型的範圍內 // 由於 2 位 16 進製表示一個字節,因此用系統 int 類型的字節長度去分組是絕對安全的 $chunk_size = PHP_INT_SIZE; // 對 hex str 作分組對齊,不然 str 的最後幾位可能會被看成低位數據處理 // 好比 fffff 以 4 位拆分 'ffff', 'f' 後 最後一組 'f' 就被低位數據處理了 // 對齊後 fffff000 分組 'ffff', 'f000' 就能保證 'f' 的數據位了 $hex_str = str_pad($hex_str, $hex_str_len + ($chunk_size - ($hex_str_len % $chunk_size)), 0, STR_PAD_RIGHT); // 防止 hexdec 時溢出 使用 PHP_INT_SIZE 個 16 進制字符一組作拆分 // 因 16 進制 2 位標識一個字節 因此 PHP_INT_SIZE 是絕對不會溢出的 $hex_str_arr = str_split($hex_str, $chunk_size); // 位數據的二進制字符串 $bitmap_bin_str = ''; array_walk($hex_str_arr, function($hex_str_chunk) use (&$bitmap_bin_str, $chunk_size) { $bitmap_bin_str .= str_pad(decbin(hexdec($hex_str_chunk)), $chunk_size * 4, 0, STR_PAD_LEFT); }); // 一次讀取redis便可拿到 bitmap O(n)次操做的數據 echo $bitmap_bin_str{0} . PHP_EOL; //第1天 echo $bitmap_bin_str{233} . PHP_EOL;//第234天 echo $bitmap_bin_str{364} . PHP_EOL;//第365天
註釋較多,業務代碼很少,多多理解~code