數據庫存儲二元值的優化方案,位存儲

在進行應用開發的時候,相信總是會有些數據的數據結構中含有一些二元屬性,如該條記錄是否對用戶可見、是否為待辦記事等等。而這些屬性也是需要存到數據庫中的。

那這樣的屬性,在內存中使用的數據結構中可能表現為一個布爾值,存到數據庫中還能是布爾類型嗎?

以Android開發為例,Android中使用的數據庫系統是SQLite,SQLite支持的存儲格式根據官網的文檔[1] 有如下這些:

官網文檔截圖

可以看到并沒有布爾類型這種存儲格式,所以開發人員常常都是用一個Integer的字段來存儲一個布爾值屬性。既然數據庫都用了Integer,許多開發人員也就把數據結構中的boolean也改成了int。

但是在Java中,一個boolean只占一個二進制位,而一個int占了32個二進制位[2] 。整整差了31倍!那這樣子的使用未免也太浪費了吧!

既然一個int都能存32位,那就直接用一個int值來存32個二元屬性。一般的數據結構都不會用到那么多個二元屬性,哪怕是進行了多次業務升級之后。而且我覺得吧,如果真的用到了那么多二元屬性,建議重新考慮數據結構設計的合理性。

實現的方式很簡明易懂,位運算。先上工具類代碼。

/**
 * 用于幫助開發者在數據結構中使用Int值存儲二元屬性的工具類
 * Created by Shawlaw on 2016/12/4.
 */

public class BitHelper {
    /**
     * 給指定的int數值的特定位置0或置1
     * @param src 用于存儲二元值的int數
     * @param bitMask 指定位的掩碼,可通過{@link #getDeterminedBitMask(int)}取得指定位的對應掩碼
     * @param bitValue 要給指定位設定的值
     * @return 置值完畢后的int數
     */
    public static int setDeterminedBit(int src, int bitMask, boolean bitValue){
        if (bitValue) {
            src |= bitMask;
        } else {
            src &= (~bitMask);
        }
        return src;
    }

    /**
     * 取得指定的int數值中的特定位上的值
     * @param src 用于存儲二元值的int數
     * @param bitMask 指定位的掩碼,可通過{@link #getDeterminedBitMask(int)}取得指定位的對應掩碼
     * @return 指定位的值是否為1,為1則返回true,為0則返回false
     */
    public static boolean getDeterminedBit(int src, int bitMask){
        return (src & bitMask ) != 0;
    }

    /**
     * 獲取int數的指定位的位運算掩碼,一般配合{@link #getDeterminedBit(int, int)}或{@link #setDeterminedBit(int, int, boolean)}方法一起使用
     * @param reverseIndex 從最右即最低位為1數起的位數,最大為32,因為int數為4字節最大32位。
     * @return 位運算掩碼
     */
    public static int getDeterminedBitMask(int reverseIndex){
        if (reverseIndex > 32 || reverseIndex < 1) {
            throw new RuntimeException("Index must between 1 to 32. The current index is "+reverseIndex);
        }
        return 1 << (reverseIndex - 1);
    }
}

工具類里面一共就三個方法,分別用于設置特定位的值、取特定位的值以及取特定位的運算掩碼。
使用樣例代碼,如下:

public class MyModel{
    private boolean mIsNewUser;
    private boolean mIsRedDotShowed;

    private int mBitStatus;

    private final static int IS_NEW_USER_BIT_MASK = BitHelper.getDeterminedBitMask(1);
    private final static int IS_RED_DOT_SHOWED_BIT_MASK = BitHelper.getDeterminedBitMask(2);

    /**
     * 從數據庫或網絡請求取到數據**之后**,執行這個方法,從數值中解析各個位的狀態到內存中的boolean值中。
     */
    public void restoreStatusFromInt(){
        mIsNewUser = BitHelper.getDeterminedBit(mBitStatus, IS_NEW_USER_BIT_MASK);
        mIsRedDotShowed = BitHelper.getDeterminedBit(mBitStatus, IS_RED_DOT_SHOWED_BIT_MASK);
    }

    /**
     * 要寫入數據庫或發網絡請求**之前**,執行這個方法,把內存中的boolean值寫入到數值中。
     */
    public void storeStatusToInt(){
        mBitStatus = BitHelper.setDeterminedBit(mBitStatus, IS_NEW_USER_BIT_MASK, mIsNewUser);
        mBitStatus = BitHelper.setDeterminedBit(mBitStatus, IS_RED_DOT_SHOWED_BIT_MASK, mIsRedDotShowed);
    }
   ....省略了通用的Setter和Getter方法....
}

講完了源代碼和樣例代碼,再來說說這樣實現的優點和缺點。
優點:

  1. 節省空間耗費。
  2. 業務彈性強,尤其是當內存數據結構發生多次變化時,數據庫表頭不用同樣地多次更改。

缺點: 對代碼調用次序有硬性要求。

所以使用的時候還是得根據開發需求來確定要不要這樣實現。


參考文獻:
[1] Datatypes In SQLite Version 3
[2] Java基本數據類型總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容