欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

位运算在角色权限设计中的应用

程序员文章站 2022-07-29 20:48:20
1.引言 2.位运算基础 3.位运算在角色权限设计中的应用 4.为什么in32的范围是-2^31 ~ 2^31-1 ? 5.同余的概念 6.模的概念帮助理解补数和补码。 一、引言 这周在做一个新增角色权限需求时,遇到下面这样一行代码,这篇文章将围绕这行代码展开。 二、位运算基础 关于位运算的基础知识 ......

1.引言

2.位运算基础

3.位运算在角色权限设计中的应用

4.为什么in32的范围是-2^31 ~ 2^31-1 ?

5.同余的概念

6.模的概念帮助理解补数和补码。 

一、引言

这周在做一个新增角色权限需求时,遇到下面这样一行代码,这篇文章将围绕这行代码展开。

user.RoleType = ~(~user.RoleType | 511) | requestDTO.Role;

二、位运算基础

关于位运算的基础知识参见:

百度百科:https://baike.baidu.com/item/%E4%BD%8D%E8%BF%90%E7%AE%97

*:https://zh.wikipedia.org/wiki/%E4%BD%8D%E6%93%8D%E4%BD%9C

总结如下:

位运算在角色权限设计中的应用

1.位逻辑非运算(记忆技巧: 二进制按位取反)
位逻辑非运算按位对运算对象的值进行非运算,即:如果某一位等于0,就将其转变为1;如果某一位等于1,就将其转变为0。
比如,对二进制的10010001进行位逻辑非运算,结果等于01101110,
用十进制表示就是:~0111(十进制7)=1000(十进制8)
 
2.位逻辑与运算(记忆技巧: 二进制按位 true&&true=true才为true)
位逻辑与运算将两个运算对象按位进行与运算。与运算的规则:1与1等于1,1与0等于0,0与0等于0。
比如:10010001(二进制)&11110000等于10010000(二进制)
 
3.位逻辑或运算(记忆技巧: 二进制按位 true||false=true,true||true=true)
位逻辑或运算将两个运算对象按位进行或运算。或运算的规则是:1或1等1,1或0等于1,
0或0等于0。比如10010001(二进制)| 11110000(二进制)等于11110001(二进制)
 
4.位逻辑异或运算(记忆技巧: 二进制按位 true||false=true 不同时true)
位逻辑异或运算将两个运算对象按位进行异或运算。异或运算的规则是:1异或1等于0,
1异或0等于1,0异或0等于0。即:相同得0,相异得1。
比如:10010001(二进制)^11110000(二进制)等于01100001(二进制)
 
5.位左移运算
位左移运算将整个数按位左移若干位,左移后空出的部分0。比如:8位的byte型变量
byte a=0x65(即二进制的01100101),将其左移3位:a<<3的结果是0x27(即二进制的00101000)
 
6.位右移运算
 位右移运算将整个数按位右移若干位,右移后空出的部分填0。比如:8位的byte型变量
Byte a=0x65(既(二进制的01100101))将其右移3位:a>>3的结果是0x0c(二进制00001100)

 三、位运算在角色权限设计中的应用(优缺点)

业务场景:有A.B.C.D四个基础角色,现在需要新增一个复合角色(架构师),可以配置用户。

下面是一个demo例子,位运算在角色权限中的应用

   [Flags]
    public enum RoleType
    {
        /// <summary>
        /// 无角色
        /// </summary>
        [Description("无角色")]
        None = 0,
        /// <summary>
        /// 普通用户角色
        /// </summary>
        [Description("普通用户")]
        A = 1,
        /// <summary>
        /// 初级开发
        /// </summary>
        [Description("初级开发")]
        B = 2,
        /// <summary>
        /// 中级开发
        /// </summary>
        [Description("中级开发")]
        C = 4,
        /// <summary>
        /// 高级开发
        /// </summary>
        [Description("高级开发")]
        D = 8,
        /// <summary>
        /// 架构师
        /// </summary>
        [Description("架构师")]
        E = 8
    }
    public class UnitTest1
    {
        public static void Test1()
        {
            var a = RoleType.A | RoleType.B;  //变量a为 A | B
            var b = RoleType.B | RoleType.D;  //变量b为 B | D
            var aa = a.ToString();//变量aa为 "A,B" 

            var bb = a & (~RoleType.A);//从组合状态中去掉一个元素A ,结果为 枚举 B
            var bb1 = ~(~a | RoleType.A); //bb结果等价于bb1
            var cc = (b & RoleType.B) != 0;//检查组合状态是否包含枚举 B  

            var dd = RoleType.A | RoleType.B | RoleType.B | RoleType.B; //变量dd为 A | B
        }
    }

分析:

1.为什么枚举角色数都是2的倍数?

十进制  二进制

1    01

2    10

4    100

8    1000

。。。。。。

我们发现在各个位上值都是唯一的,所以做位或运算时,不同值的运算结果是唯一的;反过来,我们也可以根据结果值推算出来包含的枚举(即业务中的角色)

ok,到这里我们再看开头引言中的那行代码,可以写为

user.RoleType = (user.RoleType & ~511) | requestDTO.Role;

抽象为x=(x&~y)|z,就是去除x中的y角色,再与z做位或组合。

想下,这个在保存用户角色的时候会很巧妙,就是去除用户 x(原有角色)中的 y(基础角色),再和z(要保存的角色),做位或运算组合 得出一个新的要保存角色。

优点:一个roletype字段可以保存用户的所有角色信息

缺点:当已经有31个角色,当需要再新增角色的时候,就变的尴尬了(超出了int32位)

解决办法

1.将roletype字段扩展为64位,但在系统的后期迭代阶段影响范围颇大,还是存在用完的时候

2.新增一张表,将复合角色与基础角色 这两个拆分位两个字段,单独保存两者之间关系

四、为什么in32的范围是-2^31 ~ 2^31-1 ?

为什么会介绍这个问题,因为当新增角色时,2^32超出了int32的范围,但是为什么int32范围是-2^31 ~ 2^31-1 ?本着对刨根问底的态度,便追寻了下去。

我们可以先研究下8位二进制的标识范围为什么是-2^7~2^7-1

这里要说下 原码,反码,补码的概念。

原码

正数的原码就是它的本身

假设使用一个字节存储整数,整数10的原码是:0000 1010

负数用最高位是1表示负数

假设使用一个字节存储整数,整数-10的原码是:1000 1010

反码

正数的反码跟原码一样

假设使用一个字节存储整数,整数10的反码是:0000 1010

负数的反码是负数的原码按位取反(0变1,1变0),符号位(首位)不变

假设使用一个字节存储整数,整数-10的反码是:1111 0101

补码

正数的补码和原码一样

假设使用一个字节存储整数,整数10的补码是:0000 1010(这一串是10这个整数在计算机中存储形式)

负数的补码是负数的反码加1

假设使用一个字节存储整数,整数-10的补码是:1111 0110(这一串是-10这个整数在计算机中存储形式)

 

在计算机中,为什么不用原码和反码,而是用补码呢?

使用原码计算10-10

         0000 1010  (10的原码)

    +        1000 1010   (-10的原码)

------------------------------------------------------------

         1001 0100  (结果为:-20,很显然按照原码计算答案是否定的。)

 

分析:正常的加法规则不适用于正数与负数的加法,因此必须制定两套运算规则,一套用于正数加正数,还有一套用于正数加负数。从电路上说,就是必须为加法运算做两种电路

 

使用反码计算10-10

      0000 1010  (10的反码)

    +   1111 0101  (-10的反码)

------------------------------------------------------------

      1111 1111  (计算的结果为反码,我们转换为原码的结果为:1000 0000,最终的结果为:-0,很显然按照反码计算答案也是否定的。)

使用补码计算10-10

      0000 1010  (10的补码)

   +   1111  0110  (-10的补码)

------------------------------------------------------------

      1 0000 0000  (由于我们这里使用了的1个字节存储,因此只能存储8位,最高位(第九位)那个1没有地方存,就被丢弃了。因此,结果为:0)

 

分析:补码表示法可以将加法运算规则,扩展到整个整数集,从而用一套电路就可以实现全部整数的加法。补码是计算机中存储整数的形式。

 

八位二进制正数的补码范围是0000 0000 ~ 0111 1111 即0 ~ 127

负数的补码范围是正数的原码0000 0000 ~ 0111 1111 取反加一(也可以理解为负数1000 0000 ~ 1111 1111化为反码末尾再加一)。 所以得到 1 0000 0000 ~ 1000 0001

1000 0001作为补码,其反码是1000 0000,其原码是1111 1111(-127)

依次往前推,可得到1111 1111作为补码,其反码1111 1110,原码1000 0001(-1)

那么补码0000 0000(1被舍去)的原码是1000 0000符号位同时也可以看做数字位即表示-128(-2^7)

类推:in32的范围便是-2^31 ~ 2^31-1 

 五、同余的概念

两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余

记作 a ≡ b (mod m)

读作 a 与 b 关于模 m 同余。

六、模的概念

时间不早了,模的概念可以帮助理解补数和补码,下篇博客中提到吧。。。

 

博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。

博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式写的文章,请原谅博主成为一个无耻的文档搬运工!