概述
从现代计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算都是叫位运算,即将符号位共同参与运算的运算。
Java 中有以下的运算符:
运算符 | 名称 | 示例 | 结果 | 说明 |
---|---|---|---|---|
<< | 左移 | 4<<2 | 16 | 符号左边的操作数左移指定的位数 |
>> | 右移 | 4>>1 | 2 | 将符号的左边的操作数右移指定位数 |
>>> | 无符号右移 | 4>>>1 | 0 | 将符号左边的操作数右移指定的位数 |
& | 与运算 | 4&2 | 0 | 两个二进制位,只要有一个为0,那么结果就为0,否则结果为1 |
| | 或运算 | 4!2 | 6 | 两个二进制位,只要有一个为1,那么结果就为1,否则结果为0 |
^ | 异或运算 | 4^2 | 6 | 相同二进制位,结果为0;不同的二进制位,结果为1 |
~ | 取反 | -4 | -5 | 二进制位,0变1;1变0 |
与、或、异或、取反
class Playground {
public static void main(String[ ] args) {
int a = 3, b = 4;
// 与运算
System.out.println(a & b);
// 或运算
System.out.println(a | b);
// 异或运算
System.out.println(a ^ b);
// 取反
System.out.println(~a);
}
}
a 和 b 变量都是正数,在Java中,int 是4字节32位带符号的二进制补码整数。
a 和 b,十进制为 3 和 4 ;正数的二进制最高位为 0,所以二进制为:0000 0000 0000 0000 0000 0000 0000 0011
和 0000 0000 0000 0000 0000 0000 0000 0100
。
在上述程序中的运算,使用二进制表示如下:
与运算
两个二进制位,只要有一个为0,那么结果就为0,否则结果为1
a & b
变量 | 二进制 | 十进制 |
---|---|---|
a | 0000 0000 0000 0000 0000 0000 0000 0011 | 3 |
b | 0000 0000 0000 0000 0000 0000 0000 0100 | 4 |
a & b | 0000 0000 0000 0000 0000 0000 0000 0000 | 0 |
或运算
a | b
两个二进制位,只要有一个为1,那么结果就为1,否则结果为0
变量 | 二进制 | 十进制 |
---|---|---|
a | 0000 0000 0000 0000 0000 0000 0000 0011 | 3 |
b | 0000 0000 0000 0000 0000 0000 0000 0100 | 4 |
a | b | 0000 0000 0000 0000 0000 0000 0000 0111 | 7 |
异或运算
a ^ b
相同二进制位,结果为0;不同的二进制位,结果为1
变量 | 二进制 | 十进制 |
---|---|---|
a | 0000 0000 0000 0000 0000 0000 0000 0011 | 3 |
b | 0000 0000 0000 0000 0000 0000 0000 0100 | 4 |
a ^ b | 0000 0000 0000 0000 0000 0000 0000 0111 | 7 |
取反运算
~a
0变1;1变0。
需要注意,计算机进行二进制运算,都是使用原码的补码进行运算,上述其他运算的二进制的最高为0,说明是正数,正数的补码就是原码,所以直接使用补码转十进制。
但是此例中,取反之后,最高位为1,说明此时为负数,而负数是使用补码来进行运算,所以需要将此负数的补码,转为原码,再转为十进制。
负数:原码 转 补码:原码取反; + 1;
负数:补码 转 原码:补码取反;+ 1;(补码的补码就是原码)
变量 | 二进制 | 十进制 | 说明 |
---|---|---|---|
a | 0000 0000 0000 0000 0000 0000 0000 0011 | 3 | |
取反 | 1111 1111 1111 1111 1111 1111 1111 1100 | 取反之后的二进制最高位为1,说明此时为负数,需要将此二进制位补码,需要转为原码 | |
计算原码 | 1000 0000 0000 0000 0000 0000 0000 0011 | 补码取反,符号位不变 | |
计算原码 | 1000 0000 0000 0000 0000 0000 0000 0100 | -4 | +1 |
需要注意,Integer.toBinaryString(),获取的是当前参数的补码字符串,而非原码!!!
源代码注释:The unsigned integer value is the argument plus 2^32 if the argument is negative;如果参数负数,那么返回值为 负数 + 2^32,结果就是负数的补码!!!
左移和右移
左移
左移说明:符号左边的操作数左移指定的位数。
左移语法:
操作数 << 位数
- 首先将左边的操作数转为二进制
- 然后按照要求左移指定位数,左边最高位丢弃,右边补0
3<<2
3的二进制为:
0000 0000 0000 0000 0000 0000 0000 0011
左移两位,左边最高位丢弃(红色的部分丢弃),右边补0(蓝色部分补0):
0000 0000 0000 0000 0000 0000 0000 1100
右移
将符号的左边的操作数右移指定位数。
右移语法:
操作数>>位数
- 首先将左边的操作数转为二进制
- 然后按要求右移指定位数:最高位是0,左边补齐0;最高位是1,左边补齐1
24>>2
24的二进制:
0000 0000 0000 0000 0000 0000 0001 1000
右移2位(红色部分丢弃),最高位是0,左边补齐0(蓝色部分);最高位是1,左边补齐1
0000 0000 0000 0000 0000 0000 0000 0110
无符号右移
将符号左边的操作数右移指定的位数
语法:
操作数>>>位数
- 首先将符号左边的操作数转为二进制
- 然后按照要求右移指定位数,左边始终补齐0
规律总结
- 左移 n 位 = 左移操作数 x 2 的 n 次方
- 正数右移 n 位 = 右移操作数 / 2 的 n 次方
- 负数右移/左移:先算出操作数补码,让补码右移/左移,在将补码转成原码
注意
以上运算操作,如果出现负数,需用负数的补码进行运算操作,运算结果为补码,需要再将补码转为原码才是最终结果。过程参考[[#取反运算]]
应用场景
奇偶判断(与运算)
思路:
假设二进制最低位是 n , 那么十进制换算为: n x 2^0 = 1 / 0。
二进制其他位的结果均为 2 的次方,是偶数 x,那么 x 加上最低位的结果(1 / 0)就可以判断出此数为奇偶了。偶数 + 1 = 奇数;偶数 + 偶数 = 奇数。
十进制数 1 的二进制为 0001,那么可通过 1 进行与运算判断。
例如数字 3 & 1:3 的二进制为 0011,1 的二进制为 0001。这两个二进制进行与运算,结果为二进制 0001,换成十进制也是 1,就说明此数为负数了。
3的二进制 | 0 | 0 | 1 | 1 |
---|---|---|---|---|
1的二进制 | 0 | 0 | 0 | 1 |
与运算结果 | 0 | 0 | 0 | 1 |
/**
* 判断传入的数字是否为偶数,通过与运算进行判断。
* @param number 数字
*/
public static void isEvenNumber(int number) {
if ((number & 1) == 0) {
System.out.println("偶数:" + number);
} else {
System.out.printf("奇数:" + number);
}
}
两数交换(异或运算)
思路:一个数对自己异或,再对另一个数异或,结果为另一个数。
说明:一个数对自己异或运算,结果为 0,0 再对另一个数异或,结果就为另一个数。
/**
* 将传输的两个参数值进行交换
*
* @param arg1 参数1
* @param arg2 参数2
*/public static void swap(int arg1, int arg2) {
System.out.println(arg1 ^ arg1 ^ arg2);
System.out.println(arg2 ^ arg2 ^ arg1);
}
进阶写法
/**
* 将传输的两个参数值进行交换
*
* @param arg1 参数1
* @param arg2 参数2
*/public static void swap(int arg1, int arg2) {
System.out.println("原 arg1:" + arg1);
System.out.println("原 arg2:" + arg2);
arg1 = arg1 ^ arg2;
// 此时 arg1 = arg1 ^ arg2, 所以 arg2 = arg1 ^ arg2 ^ arg2。此时 arg2 交换完成,变成了 arg1 的值
arg2 = arg1 ^ arg2;
// 此时 arg1 = arg1 ^ arg2,而 arg2 已经变成了 arg1 的值,所以:arg1 ^ arg2 ^ arg1。此时 arg1 交换完成,变成了 arg2 的值
arg1 = arg1 ^ arg2;
System.out.println("新 arg1:" + arg1);
System.out.println("新 arg2:" + arg2);
}