我們知道計算機中數據都是用二進制數存儲。二進制數是一系列0和1的組合,長整型64位,最短的字節型也有8位。其中每一位0和1都可以看做一種狀態的開和關,所以就有了這樣的一種狀態碼存儲方式:把同一對象的多種狀態按位組合到一個整數中。
例如我們最最常見的 *nix 文件權限:
第9位 | 第8位 | 第7位 | 第6位 | 第5位 | 第4位 | 第3位 | 第2位 | 第1位 | 第0位 |
---|---|---|---|---|---|---|---|---|---|
是否目錄 | 所有者讀權限 | 所有者寫權限 | 所有者執行權限 | 組讀權限 | 組寫權限 | 組執行權限 | 其餘用戶讀權限 | 其餘用戶寫權限 | 其餘用戶執行權限 |
0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
那麼這一組狀態在程序中表示爲:0b0111101101
,即八進制的 0o755
,十進制的 493
。
二進制狀態碼存儲的主要好處是節省存儲空間,相對於鍵值對(對象)存儲而言可讀性較差(當然文件權限這種另說)。這種存儲方式僅適用於“一個對象有多種狀態,每種狀態僅有兩種情況”這一情形,請不要對一種狀態多種情況的情形使用二進制狀態碼存儲方式,更不要出現十進制的 0
1
10
這種狀態碼,很蠢。。。
使用位運算操作狀態碼
基於這種存儲方式,也衍生了一些操作狀態碼的方式:
判斷第 x 位狀態是否開啓(x 以 0 開始,下同):
status & (1 << x) == 0
打開第 x 位
status |= 1 << x
關閉第 x 位
status &= ~(1 << x)
編程語言支持
某些編程語言提供了對二進制狀態碼的一些原生支持。C/C++ 提供了 位域,以及專門的模板庫 bitset 用於簡化位運算操作。C# 則提供了 Flags 特性標記某個枚舉被視作位域
另外很重要一點,JavaScript 雖然也支持位運算,但由於 JavaScript 中的 number
類型都是雙精度浮點數,在做位運算時會先將數值截斷至 32 位長度。例如很著名的數字轉整數bug:10000000000 | 0 => 1410065408
。所以注意如果後端返回二進制狀態碼讓前端判斷,確保後端使用 uint32_t 存儲