如何優雅地運用位運算實現產品需求?

原文地址:梁桂釗的博客java

博客地址:http://blog.720ui.com數據庫

歡迎關注公衆號:「服務端思惟」。一羣同頻者,一塊兒成長,一塊兒精進,打破認知的侷限性。安全

如何優雅地運用位運算實現產品需求?

在開始正文以前,咱們先來講一下 Linux 的系統權限設計。在 Linux 系統中,爲了保證文件的安全,對文件全部者、同組用戶、其餘用戶的訪問權限進行了分別管理。其中,文件全部者,即創建文件或目錄的用戶。同組用戶,是所屬組羣中的全部用戶。其餘用戶,指的是既不是文件全部者,也不是同組用戶的其餘用戶。每一個文件和目錄都具備讀取權限、寫入權限和執行權限,這三個權限之間相互獨立。數據庫設計

image.png

在 Linux 系統中,每一個文件的訪問權限能夠用 9 個字母表示,每 3 個字母表示一類用戶權限,分別表明文件建立者、同組用戶、其餘用戶。其中,r 表示讀取權限,w 表示寫入權限,x 表示執行權限。經過功能模式修改文件權限,有三個部分組成,包括對象、操做和權限。ui

image.png

假設須要增長同組用戶寫入權限,下面來看一個例子。設計

chmod g+w /root/install.log

此外,每一類用戶的訪問也能夠經過數字的方式進行表示。3d

image.png

那麼,經過數字模式就能夠對常見的 Linux 文件權限操做進行概括。code

image.png

假設須要設置建立者可讀可寫可執行、同組用戶可讀、其餘用戶可讀,咱們能夠這樣寫:對象

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 即時發送;選擇系統提醒後,支持站內信推送;選擇選擇郵箱後,該任務後續相關提醒內容,可經過發送郵件至相關人郵箱中進行通知;選擇短信後,該任務後續相關提醒內容,可經過發送短信至相關人進行通知。

image.png

咱們在設計數據庫庫表時,一般狀況下,將多個標識字段合併成一個字段,並把這個字段改爲字符串型方式保存,例如,存在 1 時表示支持 IM,2 時表示支持系統消息,3 表示支持郵箱,4 表示支持短信。此時,若是同時都知足,它的存儲形式就是以逗號分隔的字符串:「1,2,3,4」。這樣設計的好處在於,不只消除相同字段的冗餘,並且當增長新的渠道類別時,不需增長新的字段。

IM(1, "IM消息"),
SYSTEM(2, "系統提醒"),
MAIL(3, "郵箱"),
SMS(4, "短信");

但在數據查詢時,咱們須要對字符串進行分隔。而且字符串類型的字段在查詢效率和存儲空間上不如整型字段。所以,咱們能夠用「位」來解決這個問題。咱們採起不一樣的位來分別表示不一樣類別的標識字段。

image.png

所以,當某個任務支持 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;
}

總結一下,咱們在數據庫設計時,將多個標識字段合併成一個字段,並把這個字段改爲字符串型方式保存,不只消除相同字段的冗餘,並且當增長新的渠道類別時,不需增長新的字段,可是字符串類型的字段在查詢效率和存儲空間上不如整型字段。所以,咱們能夠參考用「位」來解決這個問題。咱們採起不一樣的位來分別表示不一樣類別的標識字段。

相關文章
相關標籤/搜索