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

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 同余。

六、模的概念

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

 

参考:

https://www.cnblogs.com/yinzhengjie/p/8666354.html

https://blog.csdn.net/fenzang/article/details/53500852?utm_source=itdadao&utm_medium=referral

http://www.ruanyifeng.com/blog/2009/08/twos_complement.html

http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html#!comments

posted @ 2018-05-14 01:20  快跑啊兔兔  阅读(1692)  评论(2编辑  收藏  举报