原文地址:梁桂釗的博客java
博客地址:http://blog.720ui.com數據庫
歡迎關注公衆號:「服務端思惟」。一羣同頻者,一塊兒成長,一塊兒精進,打破認知的侷限性。安全
如何優雅地運用位運算實現產品需求?
在開始正文以前,咱們先來講一下 Linux 的系統權限設計。在 Linux 系統中,爲了保證文件的安全,對文件全部者、同組用戶、其餘用戶的訪問權限進行了分別管理。其中,文件全部者,即創建文件或目錄的用戶。同組用戶,是所屬組羣中的全部用戶。其餘用戶,指的是既不是文件全部者,也不是同組用戶的其餘用戶。每一個文件和目錄都具備讀取權限、寫入權限和執行權限,這三個權限之間相互獨立。數據庫設計
在 Linux 系統中,每一個文件的訪問權限能夠用 9 個字母表示,每 3 個字母表示一類用戶權限,分別表明文件建立者、同組用戶、其餘用戶。其中,r 表示讀取權限,w 表示寫入權限,x 表示執行權限。經過功能模式修改文件權限,有三個部分組成,包括對象、操做和權限。ui
假設須要增長同組用戶寫入權限,下面來看一個例子。設計
chmod g+w /root/install.log
此外,每一類用戶的訪問也能夠經過數字的方式進行表示。3d
那麼,經過數字模式就能夠對常見的 Linux 文件權限操做進行概括。code
假設須要設置建立者可讀可寫可執行、同組用戶可讀、其餘用戶可讀,咱們能夠這樣寫:對象
chmod 755 /root/install.log
事實上,Linux 的文件訪問權限就是很是經典的位運算使用場景。無獨有偶,咱們再來看下 Java 中的 java.lang.reflect.Modifier
。其中, Modifier
類採用 16 進制定義了靜態常量。 blog
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
類提供了不少靜態方法,例如 isPublic() 方法的返回值 & PUBLIC
對應的 16 進制值,若是非 0,則說明含有 public 修飾符。
public static boolean isPublic(int mod) { return (mod & PUBLIC) != 0; }
這裏有一個重要的知識點,採用 & 運算,兩位同時爲 1,結果才爲 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
類還採用 | 運算,確保參加運算的兩個對象只要有一個爲 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 的結果是 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
書歸正傳,咱們站在前輩們的肩上,經過位運算設計優雅的多選標識,例如經過位運算實現權限控制或多狀態管理,它的好處在於易擴展,避免數據庫設計過程當中字段膨脹,減小磁盤存儲空間。
假設,咱們如今有一個有一個業務需求:在任務中添加一個通知方式,可選項包括 IM 消息、系統提醒、郵箱、短信。選擇 IM 消息後,支持 IM 即時發送;選擇系統提醒後,支持站內信推送;選擇選擇郵箱後,該任務後續相關提醒內容,可經過發送郵件至相關人郵箱中進行通知;選擇短信後,該任務後續相關提醒內容,可經過發送短信至相關人進行通知。
咱們在設計數據庫庫表時,一般狀況下,將多個標識字段合併成一個字段,並把這個字段改爲字符串型方式保存,例如,存在 1 時表示支持 IM,2 時表示支持系統消息,3 表示支持郵箱,4 表示支持短信。此時,若是同時都知足,它的存儲形式就是以逗號分隔的字符串:「1,2,3,4」。這樣設計的好處在於,不只消除相同字段的冗餘,並且當增長新的渠道類別時,不需增長新的字段。
IM(1, "IM消息"), SYSTEM(2, "系統提醒"), MAIL(3, "郵箱"), SMS(4, "短信");
但在數據查詢時,咱們須要對字符串進行分隔。而且字符串類型的字段在查詢效率和存儲空間上不如整型字段。所以,咱們能夠用「位」來解決這個問題。咱們採起不一樣的位來分別表示不一樣類別的標識字段。
所以,當某個任務支持 IM 時,則保存 1(0000 0001);支持系統消息時,則保存 2(0000 0010),支持郵箱時,則保存 4(0000 0100);支持短信時,則保存 8(0000 1000)。四種都支持,則保存 15 (0000 11111)。
位 | 值 | 說明 |
---|---|---|
00000001 | 1 | 支持IM |
00000010 | 2 | 支持系統消息 |
00000011 | 3 | 支持IM、系統消息 |
00000100 | 4 | 支持郵箱 |
00000101 | 5 | 支持郵箱、IM |
00000110 | 6 | 支持郵箱、系統消息 |
00000111 | 7 | 支持郵箱、IM、系統消息 |
00001000 | 8 | 支持短信 |
... | ||
00001111 | 15 | 支持郵箱、IM、系統消息、短信 |
緊接着,咱們經過封裝經常使用方法來實現增刪改。
/** * 判斷 * @param mod 用戶當前值 * @param value 須要判斷值 * @return 是否存在 */ public static boolean hasMark(long mod, long value) { return (mod & value) == value; } /** * 增長 * @param mod 已有值 * @param value 須要添加值 * @return 新的狀態值 */ 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; }
總結一下,咱們在數據庫設計時,將多個標識字段合併成一個字段,並把這個字段改爲字符串型方式保存,不只消除相同字段的冗餘,並且當增長新的渠道類別時,不需增長新的字段,可是字符串類型的字段在查詢效率和存儲空間上不如整型字段。所以,咱們能夠參考用「位」來解決這個問題。咱們採起不一樣的位來分別表示不一樣類別的標識字段。