位运算

作者: 浪人残风 | 来源:发表于2023-07-12 16:08 被阅读0次

java中有三种移位运算符


<<: 左移运算符,num << 1,相当于num乘以2
>>: 右移运算符,num >> 1,相当于num除以2
>>>: 无符号右移,忽略符号位,空位都以0补齐

java没有位段(位域)的类型


public class Test {
    public static void main(String[] args) {
        // 1、左移( << )
        // 0000 0000 0000 0000 0000 0000 0000 0101 然后左移2位后,低位补0://
        // 0000 0000 0000 0000 0000 0000 0001 0100 换算成10进制为20
                  int x  =  2  >> 1;
                  System.out.println(Integer.toBinaryString(x));
        System.out.println(5 << 2);// 运行结果是20
 
        // 2、右移( >> ) 高位补符号位
        // 0000 0000 0000 0000 0000 0000 0000 0101 然后右移2位,高位补0:
        // 0000 0000 0000 0000 0000 0000 0000 0001
        System.out.println(5 >> 2);// 运行结果是1
 
        // 3、无符号右移( >>> ) 高位补0
        // 例如 -5换算成二进制后为:0101 取反加1为1011
        // 1111 1111 1111 1111 1111 1111 1111 1011
        // 我们分别对5进行右移3位、 -5进行右移3位和无符号右移3位:
        System.out.println(5 >> 3);// 结果是0
        System.out.println(-5 >> 3);// 结果是-1
        System.out.println(-5 >>> 3);// 结果是536870911
 
        // 4、位与( & )
        // 位与:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
        System.out.println(5 & 3);// 结果为1
        System.out.println(4 & 1);// 结果为0
 
        // 5、位或( | )
        // 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
        System.out.println(5 | 3);// 结果为7
 
        // 6、位异或( ^ )
        // 第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0
         System.out.println(5 ^ 3);//结果为6 
 
        // 7、位非( ~ )
        // 操作数的第n位为1,那么结果的第n位为0,反之。
        System.out.println(~5);// 结果为-6 
    } 
}

在一个系统中,用户一般有查询(Select)、新增(Insert)、修改(Update)、删除(Selete)四种权限,四种权限有多种组合方式,也就是有16中不同的权限状态(2的4次方)。
一般情况下会想到用四个boolean类型变量来保存:
Permission.java


public class Permission {  
      
    // 是否允许查询  
    private boolean allowSelect;  
      
    // 是否允许新增  
    private boolean allowInsert;  
      
    // 是否允许删除  
    private boolean allowDelete;  
      
    // 是否允许更新  
    private boolean allowUpdate;  
  
    public boolean isAllowSelect() {  
        return allowSelect;  
    }  
  
    public void setAllowSelect(boolean allowSelect) {  
        this.allowSelect = allowSelect;  
    }  
  
    public boolean isAllowInsert() {  
        return allowInsert;  
    }  
  
    public void setAllowInsert(boolean allowInsert) {  
        this.allowInsert = allowInsert;  
    }  
  
    public boolean isAllowDelete() {  
        return allowDelete;  
    }  
  
    public void setAllowDelete(boolean allowDelete) {  
        this.allowDelete = allowDelete;  
    }  
  
    public boolean isAllowUpdate() {  
        return allowUpdate;  
    }  
  
    public void setAllowUpdate(boolean allowUpdate) {  
        this.allowUpdate = allowUpdate;  
    }  
}  

上面用四个boolean类型变量来保存每种权限状态。下面是另外一种方式,使用位掩码的话,用一个二进制数即可,每一位来表示一种权限,0表示无权限,1表示有权限。
NewPermission.java


public class NewPermission {  
    // 是否允许查询,二进制第1位,0表示否,1表示是  
    public static final int ALLOW_SELECT = 1 << 0; // 0001  
      
    // 是否允许新增,二进制第2位,0表示否,1表示是  
    public static final int ALLOW_INSERT = 1 << 1; // 0010  
      
    // 是否允许修改,二进制第3位,0表示否,1表示是  
    public static final int ALLOW_UPDATE = 1 << 2; // 0100  
      
    // 是否允许删除,二进制第4位,0表示否,1表示是  
    public static final int ALLOW_DELETE = 1 << 3; // 1000  
      
    // 存储目前的权限状态  
    private int flag;  
  
    /** 
     *  重新设置权限 
     */  
    public void setPermission(int permission) {  
        flag = permission;  
    }  
  
    /** 
     *  添加一项或多项权限 
     */  
    public void enable(int permission) {  
        flag |= permission;  
    }  
      
    /** 
     *  删除一项或多项权限 
     */  
    public void disable(int permission) {  
        flag &= ~permission;  
    }  
      
    /** 
     *  是否拥某些权限 
     */  
    public boolean isAllow(int permission) {  
        return (flag & permission) == permission;  
    }  
      
    /** 
     *  是否禁用了某些权限 
     */  
    public boolean isNotAllow(int permission) {  
        return (flag & permission) == 0;  
    }  
      
    /** 
     *  是否仅仅拥有某些权限 
     */  
    public boolean isOnlyAllow(int permission) {  
        return flag == permission;  
    }  
}  

NewPermission代码中,用四个常量表示了每个二进制位代码的权限项。
例如:
ALLOW_SELECT = 1 << 0 转成二进制就是0001,二进制第一位表示Select权限,
ALLOW_INSERT = 1 << 1 转成二进制就是0010,二进制第二位表示Insert权限
......
private int flag存储了各种权限的启用和停用状态,相当于代替了Permission中的四个boolean类型的变量。
用flag的四个二进制位来表示四种权限的状态,每一位的0和1代表一项权限的启用和停用:
flag 删除 修改 新增 查询
1(0001) 0 0 0 1 只允许查询(即等于ALLOW_SELECT)
2(0010) 0 0 1 0 只允许新增(即等于ALLOW_INSERT)
4(0100) 0 1 0 0 只允许修改(即等于ALLOW_UPDATE)
8(1000) 1 0 0 0 只允许删除(即等于ALLOW_DELETE)
3(0011) 0 0 1 1 只允许查询和新增
0 0 0 0 0 四项权限都不允许
15(1111) 1 1 1 1 四项权限都允许
...等等,不一一列举。
使用位掩码的方式,只需要用一个大于或等于0且小于16的整数即可表示所有的16种权限的状态。
此外,还有很多设置权限和判断权限的方法,用到了很多位运算,例如


public void enable(int permission) {  
    flag |= permission; // 相当于flag = flag | permission;  
}  

这个方法是在现有的权限基础上添加一项或多项权限。
添加一项Update权限:


permission.enable(NewPermission.ALLOW_UPDATE);  

假设现有权限只有Select,也就是flag是0001。
执行以上代码:
flag = 0001 | 0100,也就是0101,便拥有了Select和Update两项权限。
添加Insert、Update、Delete三项权限:


permission.enable(NewPermission.ALLOW_INSERT   
    | NewPermission.ALLOW_UPDATE | NewPermission.ALLOW_DELETE);  

NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE | NewPermission.ALLOW_DELETE运算结果是1110。
假设现有权限只有Select,也就是flag是0001。
flag = 0001 | 1110,也就是1111,便拥有了这四项权限,相当于添加了三项权限。
上面的设置如果使用Permission类的话,就需要下面三行代码:


permission.setAllowInsert(true);  
permission.setAllowUpdate(true);  
permission.setAllowDelete(true);  

其他方法不再一一解释。
下面对比Permission和NewPermission两种方式:
1.设置仅允许Select和Insert权限


permission.setAllowSelect(true);  
permission.setAllowInsert(true);  
permission.setAllowUpdate(false);  
permission.setAllowDelete(false);  

permission.setPermission(NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT);  

2.判断是否允许Select和Insert、Update权限


if (permission.isAllowSelect() && permission.isAllowInsert() && permission.isAllowUpdate())  

if (permission. isAllow (NewPermission.ALLOW_SELECT   
    | NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE))  

3.判断是只否允许Select和Insert权限


if (permission.isAllowSelect() && permission.isAllowInsert()   
    && !permission.isAllowUpdate() && !permission.isAllowDelete())  
if (permission. isOnlyAllow (NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT))  

二者对比可以感受到MyPermission位掩码方式相对于Permission的优势,可以节省很多代码量,位运算是底层运算,效率也非常高,而且理解起来也很简单。
附:
在Java反射中,java.lang.reflect.Modifier是用来判断类、成员变量、方法等包含的修饰符。在Modifier的源代码中可以看到:


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 boolean isProtected(int mod) {  
    return (mod & PROTECTED) != 0;  
}  
public static boolean isStatic(int mod) {  
    return (mod & STATIC) != 0;  
}  

这里使用二进制位来表示是否有某个修饰符。具体使用方式可以去看我的另一篇博文:Java反射之如何判断类或变量、方法的修饰符(Modifier解析)
此外,在Android开发中,Linkify可以设置文本中的地址、电话、邮箱等是否支持点击链接:


Linkify.addLinks(textView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);  

android.text.util.Linkify部分源码:


public static final int WEB_URLS

public static final int WEB_URLS = 0x01;  
public static final int EMAIL_ADDRESSES = 0x02;  
public static final int PHONE_NUMBERS = 0x04;  
public static final int MAP_ADDRESSES = 0x08;  
public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | MAP_ADDRESSES;  
  
public static final boolean addLinks(Spannable text, int mask) {  
    if (mask == 0) {  
        return false;  
    }  
  
    URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);  
  
    for (int i = old.length - 1; i >= 0; i--) {  
        text.removeSpan(old[i]);  
    }  
  
    ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();  
  
    if ((mask & WEB_URLS) != 0) {  
        gatherLinks(links, text, Patterns.WEB_URL,  
            new String[] { "http://", "https://", "rtsp://" },  
            sUrlMatchFilter, null);  
    }  
  
    if ((mask & EMAIL_ADDRESSES) != 0) {  
        gatherLinks(links, text, Patterns.EMAIL_ADDRESS,  
            new String[] { "mailto:" },  
            null, null);  
    }  
  
    if ((mask & PHONE_NUMBERS) != 0) {  
        gatherLinks(links, text, Patterns.PHONE,  
            new String[] { "tel:" },  
            sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);  
    }  
  
    if ((mask & MAP_ADDRESSES) != 0) {  
        gatherMapLinks(links, text);  
    }  
  
    pruneOverlaps(links);  
  
    if (links.size() == 0) {  
        return false;  
    }  
  
    for (LinkSpec link: links) {  
        applyLink(link.url, link.start, link.end, text);  
    }  
  
    return true;  
}  

相关文章

  • 3、小众运算符の大课堂(一)

    较为简单の位运算符: & 位与运算| 位或运算^ 位异或运算~ 位取反运算 举例: 要做位运算,首先要把数据转...

  • 位运算及其应用

    内容概要: 位运算基本操作 基于位运算的状态压缩 位运算经典应用 位运算解N皇后问题 位运算 符号描述规则&与1&...

  • 位运算及用位运算实现权限控制

    请自行补习位运算相关知识 位运算 位运算示例 权限控制

  • 开发基础随笔之位运算符(Bitwise Operators)

    位运算符,属于算术运算符 按位逻辑运算符: 位移运算符: 位运算符的运算数只能是整数 位移运算符:按位左移 a<<...

  • 强大的位运算符

    位取反运算符 位取反运算符(~)是对所有位的数字进行取反操作位取反运算符.png 位与运算符 位与运算符(&)可以...

  • 位运算

    位运算 1. &:按位与 规律:一假则假任何位上的数和1相&得到的结果还是那个数 2. |:按位或 规律:一真则真...

  • 位运算

    https://leetcode.com/problems/gray-code/description/这个位运算...

  • 位运算

    位运算符比一般的算术运算符速度要快,而且可以实现一些算术运算符不能实现的功能。如果要开发高效率程序,位运算符是必不...

  • 位运算

    1.不用加减乘除做加法 解法:分为三步①各位相加不进位,即先按位异或;②做进位,按位与并左移位;③结果相加,直至没...

  • 位运算

    位运算不仅可以简化某些复杂的操作,而且具有更快的计算速度。典型的应用就是除法,交换两个数值,以及在一个数组中寻找只...

网友评论

      本文标题:位运算

      本文链接:https://www.haomeiwen.com/subject/ztjpudtx.html