数据库存储二元值的优化方案,位存储

作者: Shawlaw | 来源:发表于2016-12-31 22:09 被阅读127次

    在进行应用开发的时候,相信总是会有些数据的数据结构中含有一些二元属性,如该条记录是否对用户可见、是否为待办记事等等。而这些属性也是需要存到数据库中的。

    那这样的属性,在内存中使用的数据结构中可能表现为一个布尔值,存到数据库中还能是布尔类型吗?

    以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基本数据类型总结

    相关文章

      网友评论

        本文标题:数据库存储二元值的优化方案,位存储

        本文链接:https://www.haomeiwen.com/subject/bjacmttx.html