Java位运算符

在计算机中所有数据都是以二进制的形式储存的,位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度较快。

常见位运算符说明
~取反
&按位与
|按位或
^按位异或
<<有符号左移
>>有符号右移
>>>无符号右移

一、示例详解

1.1、~ 取反

~1=0    ~0=1

注意: 这里指的是二进制上的每一位的0或1,而不是数值 0 和 1。

如:0010 ~取反后的结果为 1101; 1101 ~取反后的结果为 0010


System.out.println(~1);		// -2

为什么 1 取反的结果是 -2 呢?因为取反是相对于二进制的,我们都知道在Java中 int 是4个字节,也就是32位,并且需要明确的是在计算机中都是采用补码进行运算。


我们中的正数的原码、反码、补码是一致。

负数的反码就是他的原码除符号位外,按位取反。(符号位固定为1)
负数的补码等于反码 + 1 。

0000 0000 0000 0000 0000 0000 0000 0001   表示1补码(其正数的原码、反码、补码一致)

1111 1111 1111 1111 1111 1111 1111 1110  ~取反后的结果,为补码(要知道计算机都是采用补码进行运算)
1111 1111 1111 1111 1111 1111 1111 1101  结果减一,得到反码
1000 0000 0000 0000 0000 0000 0000 0010   按位取反拿到原码,转换为十进制为 -2(有符号位)


另外我们Java中也提供了方法可以直接打印出二进制Integer.toBinaryString(),但是需要注意的是它返回的结果是其补码,如我们输入一个负数看看(正数的原码、反码、补码一致)

System.out.println(Integer.toBinaryString(-1));
//结果为 11111111111111111111111111111111

1000 0000 0000 0000 0000 0000 0000 0001   表示 -1 的源码

1111 1111 1111 1111 1111 1111 1111 1110  除符号位,按位取反,得到 -1 的反码
1111 1111 1111 1111 1111 1111 1111 1111  结果加一,得到 -1 的补码

这样我们就可以验证出Integer.toBinaryString()返回的是其补码。


1.2、& 按位与

1&1=1    1&0=0    0&0=0

注意: 这里指的是二进制上的每一位的0或1,而不是数值 0 和 1。

如:0010 & 1100 = 0000   1110 & 0110 = 0110


这个 & 按位与 在我们取模(取余)运算中使用非常频繁

a % ( Math.pow(2,n) )  等价于  a & ( Math.pow(2,n) - 1 )

比如我们判断一个数是否是偶数,我们一般就可也以用上述方法替换

if((value % 2) == 0){}

if((value & 1) == 0){}

除了上述所说的,还有一些较为常用的方法,如:

  • 清零
    如果想将一个单元清零,只需使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。

  • 取一个数中指定位
    找一个数,对应X要取的位,该数的对应位为1,其余位为零,此数与X进行“与运算”可以得到X中的指定位。
    例如:设 X=1010 1110,取X的低4位,用 X & 0000 1111 = 0000 1110 即可得到。


1.3、| 按位或

1|1=1    1|0=1    0|0=0

注意: 这里指的是二进制上的每一位的0或1,而不是数值 0 和 1。

如:0010 | 1100 = 1110   1110 | 0110 = 1110


| 按位于常见用途: 对一个数据的某些位置1

方法:找到一个数,对应X要置1的位,该数的对应位为1,其余位为零。此数与X相或可使X中的某些位置1。
例如:将 X=1010 0000 的低4位置1 ,用 X | 0000 1111 = 1010 1111 即可得到。


1.4、^ 按位异或

0^0=0    1^0=1    0^1=1    1^1=0

注意: 这里指的是二进制上的每一位的0或1,而不是数值 0 和 1。


^ 按位异或的几个常见用途:

  • 使某些特定的位翻转
    例如对数1010 0001的第2位和第3位翻转,则可以将该数与0000 0110进行按位异或运算。
        1010 0001^0000 0110 = 1010 0111

  • 实现两个值的交换,而不必使用临时变量
    例如交换两个整数a=1010 0001,b=0000 0110的值,可通过下列语句实现:
        a = a^b;   // a=1010 0111
        b = b^a;   // b=1010 0001
        a = a^b;   // a=0000 0110

  • 快速判断两个值是否相等
    判断两个整数a,b是否相等,则可通过下列语句实现
        return ((a ^ b) == 0)


1.5、<< 有符号左移

<< 表示左移,不分正负数,低位补 0,相当于乘以 2 的幂次方。

比如 6 << 2,我们先看看 6 的二进制表示方法

0000 0110    然后将其左移两位
0001 1000

我们就得到 0001 1000 转换为十进制就是 24,也就是 6 * 2^2 的操作


1.6、>> 有符号右移

>> 表示右移,如果该数为正,则高位补0,若为负数,则高位补1;

比如 6 >> 2,我们先看看 6 的二进制表示方法

0000 0110    然后将其右移两位
0000 0001

我们就得到 0000 0001 转换为十进制就是 1,也就是 6 / 2^2 的操作


然后我们再来看看负数,如 -6 >> 2

System.out.println(-6 >> 2);	// -2

我们发现其结果为 -2 ,而不是我们预想的 -6 / 4 = -1,为什么呢?我们先看看 -6 的二进制表示方法,现在我们就 8 位来看,就不以 32位 来写了,太长了…

1000 0110     -6 的原码,除高位的符号位,其余按位取反,得到下面的反码
1111 1001     -6 的反码,反码加一,得到下面的补码
1111 1010     -6 的补码,将 -6 的补码向右移动 2 位,得到下列移动后的结果

1111 1110     -6向右移动2位后的结果,将该补码减一,得到其反码
1111 1101     -6向右移动2位后的结果的反码,将该原码除符号按位取反得其原码
1000 0010     -6向右移动2位后的结果的原码

我们就将最终得到的结果 1000 0010 转换为十进制就是 -2,我们计算中都是需要用补码进行运算,正数还好,因为原码、反码、补码都是一致的,但是负数就比较麻烦了。


1.7、>>> 无符号右移

>>> 表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

在 >> 有符号右移 中我们知道我们的符号位是不动的。若为负数,则高位补1;在 >>> 无符号右移中的区别就是,符号位也会跟着移动,然后直接补0。

所以我们发现 有符号/无符号右移中正数是没有区别的,主要是负数的区别,我们来看一看,我们刚刚解释的 -6 进行无符号右移的结果

System.out.println(-6 >>> 2);
// 1073741822

这里我们就不能以简单的8位来看来,我们要以 Java 中 int 所真正占据的32位来看

1000 0000 0000 0000 0000 0000 0000 0110   -6 的原码,除高位的符号位,其余按位取反,得到下面的反码
1111 1111 1111 1111 1111 1111 1111 1001   -6 的反码,反码加一,得到下面的补码
1111 1111 1111 1111 1111 1111 1111 1010   -6 的补码,将 -6 的补码向右移动 2 位,得到下列移动后的结果

0011 1111 1111 1111 1111 1111 1111 1110   -6 向右移动2位后的结果,该值为结果补码

注意: 我们的得到的反码,也是其原码及反码,因为他的符号位为0,是正数。


我们无符号移动得到的就是上述的结果,我们可以进行验证一下,如下

System.out.println(Integer.toBinaryString(1073741822));
// 00111111111111111111111111111110



二、应用实例

在了解完上述常见位运算符的作用后,我们可以在日常工作中进行灵活的运用,比如如果需要一个简单的权限判断,给不同的使用人员对某个功能是否可用的权限,我们第一反应就是设置对应的权限标志位即可。

private boolean allowOperation = true;

当我们不想给某个用户某个权限时,将其设置为 false 即可。


那要是一个系统中有多个权限呢?那我们就需要设置多个标志位,那未免太麻烦了点。这里如果充分的利用我们的位运算符,就可以用一个标志位就可以解决。

public class Permission {

    // 是否允许查询,二进制第1位,0表示否,1表示是
    public static final int ALLOW_SELECT = 1 << 0; // 0001  = 1

    // 是否允许新增,二进制第2位,0表示否,1表示是
    public static final int ALLOW_INSERT = 1 << 1; // 0010  = 2

    // 是否允许修改,二进制第3位,0表示否,1表示是
    public static final int ALLOW_UPDATE = 1 << 2; // 0100  =4

    // 是否允许删除,二进制第4位,0表示否,1表示是
    public static final int ALLOW_DELETE = 1 << 3; // 1000  = 8

    // 存储目前的权限状态
    private int flag;

    //设置用户的权限
    public void setPer(int per) {
        flag = per;
    }

    //增加用户的权限(1个或者多个)
    public void enable(int per) {
        flag = flag | per;
    }

    //删除用户的权限(1个或者多个)
    public void disable(int per) {
        flag = flag & ~per;
    }

    //判断用户的权限
    public boolean isAllow(int per) {
        return ((flag & per) == per);
    }

    //判断用户没有的权限
    public boolean isNotAllow(int per) {
        return ((flag & per) == 0);
    }


    public static void main(String[] args) {
        //默认给curd全部权限
        int flag = 15;
        
        Permission permission = new Permission();
        permission.setPer(flag);

        //删除其插入及删除权限
        permission.disable(ALLOW_INSERT | ALLOW_DELETE);

        //查询其是否含有curd等权限
        System.out.println("select = " + permission.isAllow(ALLOW_SELECT));   //true
        System.out.println("update = " + permission.isAllow(ALLOW_UPDATE));   //true
        System.out.println("insert = " + permission.isAllow(ALLOW_INSERT));   //false
        System.out.println("delete = " + permission.isAllow(ALLOW_DELETE));   //false

    }
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值