在进行应用开发的时候,相信总是会有些数据的数据结构中含有一些二元属性,如该条记录是否对用户可见、是否为待办记事等等。而这些属性也是需要存到数据库中的。
那这样的属性,在内存中使用的数据结构中可能表现为一个布尔值,存到数据库中还能是布尔类型吗?
以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] Datatypes In SQLite Version 3
[2] Java基本数据类型总结