如何优雅地运用位运算实现产品需求?(代码片段)

woshixiaowang woshixiaowang     2022-12-06     776

关键词:

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

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

技术图片

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

技术图片

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

chmod g+w /root/install.log

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

技术图片

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

技术图片

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

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 即时发送;选择系统提醒后,支持站内信推送;选择选择邮箱后,该任务后续相关提醒内容,可通过发送邮件至相关人邮箱中进行通知;选择短信后,该任务后续相关提醒内容,可通过发送短信至相关人进行通知。

技术图片

我们在设计数据库库表时,通常情况下,将多个标识字段合并成一个字段,并把这个字段改成字符串型方式保存,例如,存在 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(www.lafei6d.cn long mod, long value)www.tengyao3zc.cn 
    return (mod & value) =www.yixingylzc.cn= value;


/**
 * 增加
 * @param mod 已有值
 * @param value  需要添加值
 * @return 新的状态值
 */
public static long addMark(long mod, long value) 
    if (hasMark(mod, www.yacuangyl.com value))www.ued3zc.cn 
        return mod;
    
    return (mod www.keLezaix.com| value);


/**
 * 删除
 * @param mod 已有值
 * @param value  需要删除值
 * @return 新值
 */
public static long removeMark( www.hongniuyLe.cn long mod, www.letianhuanchao.cn long value) 
    if (!hasMark(mod, value))www.jintianxuesha.com 
        return mod;
    
    return mod ^ value;

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

写在末尾

【服务端思维】:我们一起聊聊服务端核心技术,探讨一线互联网的项目架构与实战经验。让所有孤军奋战的研发人员都找到属于自己的圈子,一起交流、探讨。在这里,我们可以认知升级,连接顶级的技术大牛,连接优秀的思维方式,连接解决问题的最短路径,连接一切优秀的方法,打破认知的局限。

更多精彩文章,尽在「服务端思维」!

golang使用位左移与iota计数配合可优雅地实现存储单位的常量枚举(代码片段)

查看详情

程序员如何和产品经理优雅的干架

...对这个需求本身一些看法。下面进入今天的主题:程序员如何和产品经理优雅的干架(这里优雅的干架,主要是有效的沟通)每次产品来提需求时,是这样的每次产品来改需求时,是这样的我在初出茅庐的时候,总是被产品牵着... 查看详情

位运算的运用场景使用总结(代码片段)

...码补码设计意义位运算基础Java支持的7个位运算符运算符如何使用使用场景1.判断奇偶数2.交换两个数3.找出没有重复的数4.m的n次方5.求绝对值6.取模运算7.乘法运算8.除法运算转化成位运算9.求相反数10.HashMap中哈希算法的使用11.经... 查看详情

mvi架构封装:快速优雅地实现网络请求(代码片段)

...xff0c;异常捕获等一些问题。我们这次一起来看下MVI架构下如何对网络请求进行封装,以及相对于MVVM架构有什么优势本文主要包括以下内 查看详情

如何优雅地实现环形缓冲区?(代码片段)

循环缓冲区是嵌入式软件工程师在日常开发过程中的关键组件。多年来,互联网上出现了许多不同的循环缓冲区实现和示例。我非常喜欢这个模块,可以GitHub上找到这个开源的CBUF.h模块。地址:https://github.com/barraq/BRBr... 查看详情

如何优雅地关闭资源(代码片段)

很多时候我们都会用到io资源,比如文件、网络、各种连接等。比如有时候我们需要从一个文本文件中读取数据,一般的步骤是:用FileReader打开文件包装成BufferReader循环地从BufferReader中读取内容,直接读出来的内容为空关闭Buffer... 查看详情

如何使用 Java Optional 优雅地替换三元运算符

】如何使用JavaOptional优雅地替换三元运算符【英文标题】:HowtouseJavaOptionaltoelegantlyreplaceTernaryoperators【发布时间】:2019-04-1212:37:13【问题描述】:一个超级简单的问题:这是我使用传统三元运算符?的纯Java代码publicDateTimegetCreatedA... 查看详情

mvi架构封装:快速优雅地实现网络请求(代码片段)

...xff0c;异常捕获等一些问题。我们这次一起来看下MVI架构下如何对网络请求进行封装,以及相对于MVVM架构有什么优势本文主要包括以下内容MVVM架构下的网络请求封装与问题MVI架构下封装网络请求MVI架构与Flow结合实现网络请求M... 查看详情

多线程如何优雅地初始化全局变量?(代码片段)

需求场景如果使用多线程,那么几乎都会用到全局变量,这时初始化全局变量的技巧就很重要了。通常初始化全局变量时就是像下面这样的,先判断是否已经初始化过了,然后才去初始化。在单线程场景下,lazy初始化(就是用到... 查看详情

如何优雅的实现“查看更多”(代码片段)

开始前大家做一些文本简介展示需求时可能会遇到文本过长的场景,这时视觉同学可能会要求设置最大行数并在末尾展示"查看更多"(后面简称MoreText)。废话不多说,先看下要求实现的效果(图为实现后的Dem... 查看详情

vue中点击空白处隐藏弹框(用指令优雅地实现)(代码片段)

...弹框经常性出现,并要求点击弹框外面,关闭弹框,那么如何实现呢?且听我一一。。。不了,能实现效果就好<template><div><divclass="show"v-show="show"v-clickoutside="handleClose">显示</div></div></template><s 查看详情

如何在go中优雅关闭子进程(代码片段)

有时我们会遇到这样的需求,在一个主进程中启动另外一个进程,而在Go中可以使用exec包的Cmd来轻松实现这类需求,例如代码:packagemainimport("fmt""log""os""os/exec""os/signal")funcmain()cmd:=exec.CmdPath:"nc",Args:[]string"-u","-l","8888",Dir:"/usr/bin",i... 查看详情

如何优雅地校验后端接口数据,不做前端背锅侠(代码片段)

背景最近新接手了一批项目,还没来得及接新需求,一大堆bug就接踵而至,仔细一看,应该返回数组的字段返回了null,或者没有返回,甚至返回了字符串"null"???这我能忍?我立刻截图发到群里,用红框加大加粗重点标出。... 查看详情

如何优雅地记录操作日志(代码片段)

...统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?上面这些都是本文要回答的问题。我们主要围绕着如何... 查看详情

如何优雅地记录操作日志?(代码片段)

...统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?上面这些都是本文要回答的问题。我们主要围绕着如何... 查看详情

springbootjava优雅地实现接口数据校验(代码片段)

在工作中写过Java程序的朋友都知道,目前使用Java开发服务最主流的方式就是通过SpringMVC定义一个Controller层接口,并将接口请求或返回参数分别定义在一个Java实体类中,这样SpringMVC在接收到Http请求(POST/GET)后,就... 查看详情

如何优雅地使用minicom(代码片段)

minicom简介安装minicom是linux下一款常用的串口调试工具。ubuntu环境下,使用如下命令安装sudoapt-getinstallminicom配置使用前需要进行配置,执行sudominicom-s可打开minicom并进入配置模式,使用方向键,选择需要配置的项目,如Serialportsetu... 查看详情

如何在vue中优雅地使用v-if判断(代码片段)

情况一:做vue项目,有的时候会遇到有几个元素都使用同一个v-if条件。下面这种方法虽然可以实现,但是整体代码看起来有点笨拙,我们可以用<template>标签进行优化一下。<template><divclass="card"... 查看详情