原文地址:梁桂钊的博客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; }
总结一下,咱们在数据库设计时,将多个标识字段合并成一个字段,并把这个字段改为字符串型方式保存,不只消除相同字段的冗余,并且当增长新的渠道类别时,不需增长新的字段,可是字符串类型的字段在查询效率和存储空间上不如整型字段。所以,咱们能够参考用“位”来解决这个问题。咱们采起不一样的位来分别表示不一样类别的标识字段。