原文地址:梁桂釗的博客
歡迎關(guān)注公眾號:「服務(wù)端思維」。一群同頻者,一起成長,一起精進(jìn),打破認(rèn)知的局限性。
如何優(yōu)雅地運(yùn)用位運(yùn)算實現(xiàn)產(chǎn)品需求?
在開始正文之前,我們先來說一下 Linux 的系統(tǒng)權(quán)限設(shè)計。在 Linux 系統(tǒng)中,為了保證文件的安全,對文件所有者、同組用戶、其他用戶的訪問權(quán)限進(jìn)行了分別管理。其中,文件所有者,即建立文件或目錄的用戶。同組用戶,是所屬組群中的所有用戶。其他用戶,指的是既不是文件所有者,也不是同組用戶的其他用戶。每個文件和目錄都具有讀取權(quán)限、寫入權(quán)限和執(zhí)行權(quán)限,這三個權(quán)限之間相互獨(dú)立。

在 Linux 系統(tǒng)中,每個文件的訪問權(quán)限可以用 9 個字母表示,每 3 個字母表示一類用戶權(quán)限,分別代表文件創(chuàng)建者、同組用戶、其他用戶。其中,r 表示讀取權(quán)限,w 表示寫入權(quán)限,x 表示執(zhí)行權(quán)限。通過功能模式修改文件權(quán)限,有三個部分組成,包括對象、操作和權(quán)限。

假設(shè)需要增加同組用戶寫入權(quán)限,下面來看一個例子。
chmod g+w /root/install.log
此外,每一類用戶的訪問也可以通過數(shù)字的方式進(jìn)行表示。

那么,通過數(shù)字模式就可以對常見的 Linux 文件權(quán)限操作進(jìn)行歸納。

假設(shè)需要設(shè)置創(chuàng)建者可讀可寫可執(zhí)行、同組用戶可讀、其他用戶可讀,我們可以這樣寫:
chmod 755 /root/install.log
事實上,Linux 的文件訪問權(quán)限就是非常經(jīng)典的位運(yùn)算使用場景。無獨(dú)有偶,我們再來看下 Java 中的 java.lang.reflect.Modifier 。其中, Modifier 類采用 16 進(jìn)制定義了靜態(tài)常量。
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
...
緊接著,Modifier 類提供了很多靜態(tài)方法,例如 isPublic() 方法的返回值 & PUBLIC 對應(yīng)的 16 進(jìn)制值,如果非 0,則說明含有 public 修飾符。
public static boolean isPublic(int mod) {
return (mod & PUBLIC) != 0;
}
這里有一個重要的知識點,采用 & 運(yùn)算,兩位同時為 1,結(jié)果才為 1,否則為 0。即 0&0=0; 0&1=0; 1&0=0; 1&1=1。例如:3&1 即 0000 0011 & 0000 0001 = 00000001,值為 1。
0000 0011
& 0000 0001
= 0000 0001
與此同時,Modifier 類還采用 | 運(yùn)算,確保參加運(yùn)算的兩個對象只要有一個為 1,其值為 1。即 0|0=0; 0|1=1; 1|0=1;1|1=1。例如 Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE | Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL | Modifier.STRICT 的結(jié)果是 3103,即 110000011111。
private static final int CLASS_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL |
Modifier.STRICT;
0000 0000 0000 0001
| 0000 0000 0000 0010
| 0000 0000 0000 0100
| 0000 0000 0000 1000
| 0000 0000 0001 0000
| 0000 0100 0000 0000
| 0000 1000 0000 0000
= 0000 1100 0001 1111
書歸正傳,我們站在前輩們的肩上,通過位運(yùn)算設(shè)計優(yōu)雅的多選標(biāo)識,例如通過位運(yùn)算實現(xiàn)權(quán)限控制或多狀態(tài)管理,它的好處在于易擴(kuò)展,避免數(shù)據(jù)庫設(shè)計過程中字段膨脹,減少磁盤存儲空間。
假設(shè),我們現(xiàn)在有一個有一個業(yè)務(wù)需求:在任務(wù)中添加一個通知方式,可選項包括 IM 消息、系統(tǒng)提醒、郵箱、短信。選擇 IM 消息后,支持 IM 即時發(fā)送;選擇系統(tǒng)提醒后,支持站內(nèi)信推送;選擇選擇郵箱后,該任務(wù)后續(xù)相關(guān)提醒內(nèi)容,可通過發(fā)送郵件至相關(guān)人郵箱中進(jìn)行通知;選擇短信后,該任務(wù)后續(xù)相關(guān)提醒內(nèi)容,可通過發(fā)送短信至相關(guān)人進(jìn)行通知。

我們在設(shè)計數(shù)據(jù)庫庫表時,通常情況下,將多個標(biāo)識字段合并成一個字段,并把這個字段改成字符串型方式保存,例如,存在 1 時表示支持 IM,2 時表示支持系統(tǒng)消息,3 表示支持郵箱,4 表示支持短信。此時,如果同時都滿足,它的存儲形式就是以逗號分隔的字符串:“1,2,3,4”。這樣設(shè)計的好處在于,不僅消除相同字段的冗余,而且當(dāng)增加新的渠道類別時,不需增加新的字段。
IM(1, "IM消息"),
SYSTEM(2, "系統(tǒng)提醒"),
MAIL(3, "郵箱"),
SMS(4, "短信");
但在數(shù)據(jù)查詢時,我們需要對字符串進(jìn)行分隔。并且字符串類型的字段在查詢效率和存儲空間上不如整型字段。因此,我們可以用“位”來解決這個問題。我們采取不同的位來分別表示不同類別的標(biāo)識字段。

因此,當(dāng)某個任務(wù)支持 IM 時,則保存 1(0000 0001);支持系統(tǒng)消息時,則保存 2(0000 0010),支持郵箱時,則保存 4(0000 0100);支持短信時,則保存 8(0000 1000)。四種都支持,則保存 15 (0000 11111)。
| 位 | 值 | 說明 |
|---|---|---|
| 00000001 | 1 | 支持IM |
| 00000010 | 2 | 支持系統(tǒng)消息 |
| 00000011 | 3 | 支持IM、系統(tǒng)消息 |
| 00000100 | 4 | 支持郵箱 |
| 00000101 | 5 | 支持郵箱、IM |
| 00000110 | 6 | 支持郵箱、系統(tǒng)消息 |
| 00000111 | 7 | 支持郵箱、IM、系統(tǒng)消息 |
| 00001000 | 8 | 支持短信 |
| ... | ||
| 00001111 | 15 | 支持郵箱、IM、系統(tǒng)消息、短信 |
緊接著,我們通過封裝常用方法來實現(xiàn)增刪改。
/**
* 判斷
* @param mod 用戶當(dāng)前值
* @param value 需要判斷值
* @return 是否存在
*/
public static boolean hasMark(long mod, long value) {
return (mod & value) == value;
}
/**
* 增加
* @param mod 已有值
* @param value 需要添加值
* @return 新的狀態(tài)值
*/
public static long addMark(long mod, long value) {
if (hasMark(mod, value)) {
return mod;
}
return (mod | value);
}
/**
* 刪除
* @param mod 已有值
* @param value 需要刪除值
* @return 新值
*/
public static long removeMark(long mod, long value) {
if (!hasMark(mod, value)) {
return mod;
}
return mod ^ value;
}
總結(jié)一下,我們在數(shù)據(jù)庫設(shè)計時,將多個標(biāo)識字段合并成一個字段,并把這個字段改成字符串型方式保存,不僅消除相同字段的冗余,而且當(dāng)增加新的渠道類別時,不需增加新的字段,但是字符串類型的字段在查詢效率和存儲空間上不如整型字段。因此,我們可以參考用“位”來解決這個問題。我們采取不同的位來分別表示不同類別的標(biāo)識字段。
寫在末尾
【服務(wù)端思維】:我們一起聊聊服務(wù)端核心技術(shù),探討一線互聯(lián)網(wǎng)的項目架構(gòu)與實戰(zhàn)經(jīng)驗。讓所有孤軍奮戰(zhàn)的研發(fā)人員都找到屬于自己的圈子,一起交流、探討。在這里,我們可以認(rèn)知升級,連接頂級的技術(shù)大牛,連接優(yōu)秀的思維方式,連接解決問題的最短路徑,連接一切優(yōu)秀的方法,打破認(rèn)知的局限。
更多精彩文章,盡在「服務(wù)端思維」!
