在计算机中所有数据都是以二进制的形式储存的,位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度较快。
常见位运算符 | 说明 |
---|---|
~ | 取反 |
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
<< | 有符号左移 |
>> | 有符号右移 |
>>> | 无符号右移 |
一、示例详解
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
}
}