zoukankan      html  css  js  c++  java
  • 位运算在角色权限设计中的应用(转)

    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 = 16   //纠正原文错误
        }
        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/jdzhang/p/9034171.html

  • 相关阅读:
    类型约束的本质:泛型是不完备类型,只有合乎要求的构造才能正确使用和访问。
    函数的泛型约束是函数签名的一部分,不符合约束的初始调用将不能查找到函数(报错)
    泛型约束-swift
    swift语言的特点(相对于oc)
    extension Kingfisher where Base: Image:泛型类型的具体化与实例化
    “标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法
    int 和bigint差别有多大?
    MySql5.7 配置文件 my.cnf 设置
    关于mysql 出现 1264 Out of range value for column 错误的解决办法
    git中Please enter a commit message to explain why this merge is necessary.
  • 原文地址:https://www.cnblogs.com/hzz521/p/10148371.html
Copyright © 2011-2022 走看看