<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在我們平時開發過程中,會有一些 bool 型數據需要存取,比如用戶一年的簽到記錄,簽了是 1,沒簽是 0,要記錄 365 天。如果使用普通的 key/value,每個用戶要記錄 365 個,當用戶上億的時候,需要的存儲空間是驚人的。 為了解決這個問題,Redis 提供了位圖數據結構,這樣每天的簽到記錄只占據一個位,365 天就是 365 個位,46 個字節 (一個稍長一點的字符串) 就可以完全容納下,這就大大節約了存儲空間。 ![](https://user-gold-cdn.xitu.io/2018/7/2/1645926f4520d0ce?imageslim) 位圖不是特殊的數據結構,它的內容其實就是普通的字符串,也就是 byte 數組。我們可以使用普通的 get/set 直接獲取和設置整個位圖的內容,也可以使用位圖操作 getbit/setbit 等將 byte 數組看成「位數組」來處理。 當我們要統計月活的時候,因為需要去重,需要使用 set 來記錄所有活躍用戶的 id,這非常浪費內存。這時就可以考慮使用位圖來標記用戶的活躍狀態。每個用戶會都在這個位圖的一個確定位置上,0 表示不活躍,1 表示活躍。然后到月底遍歷一次位圖就可以得到月度活躍用戶數。不過這個方法也是有條件的,那就是 userid 是整數連續的,并且活躍占比較高,否則可能得不償失。 本節略顯枯燥,如果讀者看的有點蒙,這是正常現象,讀者可以跳過閱讀下一節。以老錢的經驗,在面試中有 Redis 位圖使用經驗的同學很少,如果你對 Redis 的位圖有所了解,它將會是你的面試加分項。 ## 基本使用 Redis 的位數組是自動擴展,如果設置了某個偏移位置超出了現有的內容范圍,就會自動將位數組進行零擴充。 接下來我們使用位操作將字符串設置為 hello (不是直接使用 set 指令),首先我們需要得到 hello 的 ASCII 碼,用 Python 命令行可以很方便地得到每個字符的 ASCII 碼的二進制值。 ~~~ >>> bin(ord('h')) '0b1101000' # 高位 -> 低位 >>> bin(ord('e')) '0b1100101' >>> bin(ord('l')) '0b1101100' >>> bin(ord('l')) '0b1101100' >>> bin(ord('o')) '0b1101111' ~~~ ![](https://user-gold-cdn.xitu.io/2018/7/2/16459860644097de?imageslim) 接下來我們使用 redis-cli 設置第一個字符,也就是位數組的前 8 位,我們只需要設置值為 1 的位,如上圖所示,h 字符只有 1/2/4 位需要設置,e 字符只有 9/10/13/15 位需要設置。值得注意的是位數組的順序和字符的位順序是相反的。 ~~~ 127.0.0.1:6379> setbit s 1 1 (integer) 0 127.0.0.1:6379> setbit s 2 1 (integer) 0 127.0.0.1:6379> setbit s 4 1 (integer) 0 127.0.0.1:6379> setbit s 9 1 (integer) 0 127.0.0.1:6379> setbit s 10 1 (integer) 0 127.0.0.1:6379> setbit s 13 1 (integer) 0 127.0.0.1:6379> setbit s 15 1 (integer) 0 127.0.0.1:6379> get s "he" ~~~ 上面這個例子可以理解為「零存整取」,同樣我們還也可以「零存零取」,「整存零取」。「零存」就是使用 setbit 對位值進行逐個設置,「整存」就是使用字符串一次性填充所有位數組,覆蓋掉舊值。 **零存零取** ~~~ 127.0.0.1:6379> setbit w 1 1 (integer) 0 127.0.0.1:6379> setbit w 2 1 (integer) 0 127.0.0.1:6379> setbit w 4 1 (integer) 0 127.0.0.1:6379> getbit w 1 # 獲取某個具體位置的值 0/1 (integer) 1 127.0.0.1:6379> getbit w 2 (integer) 1 127.0.0.1:6379> getbit w 4 (integer) 1 127.0.0.1:6379> getbit w 5 (integer) 0 ~~~ **整存零取** ~~~ 127.0.0.1:6379> set w h # 整存 (integer) 0 127.0.0.1:6379> getbit w 1 (integer) 1 127.0.0.1:6379> getbit w 2 (integer) 1 127.0.0.1:6379> getbit w 4 (integer) 1 127.0.0.1:6379> getbit w 5 (integer) 0 ~~~ 如果對應位的字節是不可打印字符,redis-cli 會顯示該字符的 16 進制形式。 ~~~ 127.0.0.1:6379> setbit x 0 1 (integer) 0 127.0.0.1:6379> setbit x 1 1 (integer) 0 127.0.0.1:6379> get x "\xc0" ~~~ ## 統計和查找 Redis 提供了位圖統計指令 bitcount 和位圖查找指令 bitpos,bitcount 用來統計指定位置范圍內 1 的個數,bitpos 用來查找指定范圍內出現的第一個 0 或 1。 比如我們可以通過 bitcount 統計用戶一共簽到了多少天,通過 bitpos 指令查找用戶從哪一天開始第一次簽到。如果指定了范圍參數`[start, end]`,就可以統計在某個時間范圍內用戶簽到了多少天,用戶自某天以后的哪天開始簽到。 遺憾的是, start 和 end 參數是字節索引,也就是說指定的位范圍必須是 8 的倍數,而不能任意指定。這很奇怪,我表示不是很能理解 Antirez 為什么要這樣設計。因為這個設計,我們無法直接計算某個月內用戶簽到了多少天,而必須要將這個月所覆蓋的字節內容全部取出來 (getrange 可以取出字符串的子串) 然后在內存里進行統計,這個非常繁瑣。 接下來我們簡單試用一下 bitcount 指令和 bitpos 指令: ~~~ 127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitcount w (integer) 21 127.0.0.1:6379> bitcount w 0 0 # 第一個字符中 1 的位數 (integer) 3 127.0.0.1:6379> bitcount w 0 1 # 前兩個字符中 1 的位數 (integer) 7 127.0.0.1:6379> bitpos w 0 # 第一個 0 位 (integer) 0 127.0.0.1:6379> bitpos w 1 # 第一個 1 位 (integer) 1 127.0.0.1:6379> bitpos w 1 1 1 # 從第二個字符算起,第一個 1 位 (integer) 9 127.0.0.1:6379> bitpos w 1 2 2 # 從第三個字符算起,第一個 1 位 (integer) 17 ~~~ ## 魔術指令 bitfield 前文我們設置 (setbit) 和獲取 (getbit) 指定位的值都是單個位的,如果要一次操作多個位,就必須使用管道來處理。 不過 Redis 的 3.2 版本以后新增了一個功能強大的指令,有了這條指令,不用管道也可以一次進行多個位的操作。 bitfield 有三個子指令,分別是 get/set/incrby,它們都可以對指定位片段進行讀寫,但是最多只能處理 64 個連續的位,如果超過 64 位,就得使用多個子指令,bitfield 可以一次執行多個子指令。 ![](https://user-gold-cdn.xitu.io/2018/7/2/1645987653a2b337?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 接下來我們對照著上面的圖看個簡單的例子: ~~~ 127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w get u4 0 # 從第一個位開始取 4 個位,結果是無符號數 (u) (integer) 6 127.0.0.1:6379> bitfield w get u3 2 # 從第三個位開始取 3 個位,結果是無符號數 (u) (integer) 5 127.0.0.1:6379> bitfield w get i4 0 # 從第一個位開始取 4 個位,結果是有符號數 (i) 1) (integer) 6 127.0.0.1:6379> bitfield w get i3 2 # 從第三個位開始取 3 個位,結果是有符號數 (i) 1) (integer) -3 ~~~ 所謂有符號數是指獲取的位數組中第一個位是符號位,剩下的才是值。如果第一位是 1,那就是負數。無符號數表示非負數,沒有符號位,獲取的位數組全部都是值。有符號數最多可以獲取 64 位,無符號數只能獲取 63 位 (因為 Redis 協議中的 integer 是有符號數,最大 64 位,不能傳遞 64 位無符號值)。如果超出位數限制,Redis 就會告訴你參數錯誤。 接下來我們一次執行多個子指令: ~~~ 127.0.0.1:6379> bitfield w get u4 0 get u3 2 get i4 0 get i3 2 1) (integer) 6 2) (integer) 5 3) (integer) 6 4) (integer) -3 ~~~ wow,很魔法有沒有! 然后我們使用 set 子指令將第二個字符 e 改成 a,a 的 ASCII 碼是 97,返回舊值。 ~~~ 127.0.0.1:6379> bitfield w set u8 8 97 # 從第 9 個位開始,將接下來的 8 個位用無符號數 97 替換 1) (integer) 101 127.0.0.1:6379> get w "hallo" ~~~ 再看第三個子指令 incrby,它用來對指定范圍的位進行自增操作。既然提到自增,就有可能出現溢出。如果增加了正數,會出現上溢,如果增加的是負數,就會出現下溢出。Redis 默認的處理是折返。如果出現了溢出,就將溢出的符號位丟掉。如果是 8 位無符號數 255,加 1 后就會溢出,會全部變零。如果是 8 位有符號數 127,加 1 后就會溢出變成 -128。 接下來我們實踐一下這個子指令 incrby : ~~~ 127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w incrby u4 2 1 # 從第三個位開始,對接下來的 4 位無符號數 +1 1) (integer) 11 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 12 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 13 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 14 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 15 127.0.0.1:6379> bitfield w incrby u4 2 1 # 溢出折返了 1) (integer) 0 ~~~ bitfield 指令提供了溢出策略子指令 overflow,用戶可以選擇溢出行為,默認是折返 (wrap),還可以選擇失敗 (fail) 報錯不執行,以及飽和截斷 (sat),超過了范圍就停留在最大最小值。overflow 指令只影響接下來的第一條指令,這條指令執行完后溢出策略會變成默認值折返 (wrap)。 接下來我們分別試試這兩個策略的行為 **飽和截斷 SAT** ~~~ 127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 11 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 12 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 13 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 14 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 15 127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 # 保持最大值 1) (integer) 15 ~~~ **失敗不執行 FAIL** ~~~ 127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 11 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 12 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 13 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 14 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 15 127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 # 不執行 1) (nil) ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看