文档章节

如何优雅地运用位运算实现产品需求?

梁桂钊
 梁桂钊
发布于 07/10 06:58
字数 1755
阅读 2.4W
收藏 57

「深度学习福利」大神带你进阶工程师,立即查看>>>

原文地址:梁桂钊的博客

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

欢迎关注公众号:「服务端思维」。一群同频者,一起成长,一起精进,打破认知的局限性。

如何优雅地运用位运算实现产品需求?

在开始正文之前,我们先来说一下 Linux 的系统权限设计。在 Linux 系统中,为了保证文件的安全,对文件所有者、同组用户、其他用户的访问权限进行了分别管理。其中,文件所有者,即建立文件或目录的用户。同组用户,是所属组群中的所有用户。其他用户,指的是既不是文件所有者,也不是同组用户的其他用户。每个文件和目录都具有读取权限、写入权限和执行权限,这三个权限之间相互独立。

image.png

在 Linux 系统中,每个文件的访问权限可以用 9 个字母表示,每 3 个字母表示一类用户权限,分别代表文件创建者、同组用户、其他用户。其中,r 表示读取权限,w 表示写入权限,x 表示执行权限。通过功能模式修改文件权限,有三个部分组成,包括对象、操作和权限。

image.png

假设需要增加同组用户写入权限,下面来看一个例子。

chmod g+w /root/install.log

此外,每一类用户的访问也可以通过数字的方式进行表示。

image.png

那么,通过数字模式就可以对常见的 Linux 文件权限操作进行归纳。

image.png

假设需要设置创建者可读可写可执行、同组用户可读、其他用户可读,我们可以这样写:

chmod 755 /root/install.log

事实上,Linux 的文件访问权限就是非常经典的位运算使用场景。无独有偶,我们再来看下 Java 中的 java.lang.reflect.Modifier 。其中, Modifier 类采用 16 进制定义了静态常量。 

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;
}

总结一下,我们在数据库设计时,将多个标识字段合并成一个字段,并把这个字段改成字符串型方式保存,不仅消除相同字段的冗余,而且当增加新的渠道类别时,不需增加新的字段,但是字符串类型的字段在查询效率和存储空间上不如整型字段。因此,我们可以参考用“位”来解决这个问题。我们采取不同的位来分别表示不同类别的标识字段。

梁桂钊
粉丝 24
博文 11
码字总数 46658
作品 0
杭州
私信 提问
加载中
此博客有 22 条评论,请先登录后再查看。
用vertx实现高吞吐量的站点计数器

工具:vertx,redis,mongodb,log4j 源代码地址:https://github.com/jianglibo/visitrank 先看架构图: 如果你不熟悉vertx,请先google一下。我这里将vertx当作一个容器,上面所有的圆圈要...

jianglibo
2014/04/03
4.3K
3
SQLServer实现split分割字符串到列

网上已有人实现sqlserver的split函数可将字符串分割成行,但是我们习惯了split返回数组或者列表,因此这里对其做一些改动,最终实现也许不尽如意,但是也能解决一些问题。 先贴上某大牛写的s...

cwalet
2014/05/21
9.7K
0
Swift百万线程攻破单例(Singleton)模式

一、不安全的单例实现 在上一篇文章我们给出了单例的设计模式,直接给出了线程安全的实现方法。单例的实现有多种方法,如下面: class SwiftSingleton { } 这段代码的实现,在shared中进行条...

一叶博客
2014/06/20
3.5K
16
表单验证插件--Jquery表单验证插件

目前支持对以下格式的值进行验证: cnum-(纯数字), char-(纯字母), zwen-(中文), bysc-(字母开头), mail(邮箱), yzbm(邮政编码) 其中的‘’表示长度,比如“zwen1-5”表示中文1-5位的长度。同...

huanganiu
2013/01/23
4.4K
0
mvc框架--Razor

Razor 是一个轻巧而优雅的servlet mvc框架 # 又一个轮子? no,写就她是为了证实我个人的某些想法,并在这个过程中练练手,这两种冲动碰撞在一起,自然而然地产生了Razor # Razor的现在和未来...

dtubest
2013/01/25
3.1K
0

没有更多内容

加载失败,请刷新页面

加载更多

《ClickHouse 源码阅读 —— SQL的前世今生》

简介: 作者:逍凯 注:以下分析基于开源 v19.15.2.2-stable 版本进行,社区最新版本代码改动较大,但是总体思路是不变的。 用户提交一条查询SQL背后发生了什么? 在传统关系型数据库中,SQL...

一肥仔
昨天
4
0
神的编辑器 Emacs 快捷键

基础命令 C-x C-c 退出 C-x C-s 保存 C-x C-w 另存为 C-x C-f 打开文件 C-g 取消当前操作 C-x u 撤销 移动命令 M-Shift-< 文档开头 M-Shift-> 文档结尾 C-a 行首 C-e 行尾 剪切复制粘贴 C-sp...

lemos
昨天
13
0
GeoPandas入门 | 03-空间关系和操作

03-空间关系和操作 源代码请看此处 %matplotlib inlineimport pandas as pdimport geopandas countries = geopandas.read_file("zip://./data/ne_110m_admin_0_countries.zip")cities =......

酱肉包-
昨天
8
0
检查Ruby中的数组中是否存在值 - Check if a value exists in an array in Ruby

问题: I have a value 'Dog' and an array ['Cat', 'Dog', 'Bird'] . 我有一个值'Dog'和一个阵列['Cat', 'Dog', 'Bird'] 。 How do I check if it exists in the array without looping thr......

fyin1314
昨天
9
0
最新情报:所有的递归都可以改写成非递归?

前言 本文收录于专辑:http://dwz.win/HjK,点击解锁更多数据结构与算法的知识。 你好,我是彤哥,一个每天爬二十六层楼还不忘读源码的硬核男人。 上一节,我们使用位图介绍了12306抢票算法的...

彤哥读源码
昨天
19
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部