位运算是对整数的二进制位进行操作的一种运算。在java中long, int, short, char和byte类型都可以使用位运算。
位运算的过程如下:首先将十进制整数转换成二进制表示形式,然后将位运算符应用于每个二进制数位,并计算结果。最后,将二进制结果转换回其十进制表示。
位运算符有以下几种:与运算(&)、或运算(|)、异或运算(^)、取反运算(~)、左移(<<)、右移(>>)等。下面来分别看下每个运算。
移位运算
移位运算有三种
左移(<<):将二进制位向左移动指定的位数,右侧用0填充。
java
int a = 5;//二进制101
a = a << 1;//左移一位,右补0 = 1010,换算成10进制变成10
System.out.println(a);
右移(>>):将二进制位向右移动指定的位数,保持符号位。
java
int b = 8;//二进制 100
b = b >> 1;//右移一位,变成 10 转成十进制=4
System.out.println(b);
无符号右移(>>>):将二进制位向右移动指定的位数,左侧用0填充,不考虑符号位。
我们知道对于有符号整数,第一位为符号位。正数符号位为0,负数的符号位为1。这里无符号右移左侧补0会将负数变成一个正数。原来是正数的数无符号右移和普通的右移没有区别。
java
int a = -8; // 二进制: 1111...1000
int result = a >>> 1; // 结果: 2147483644 (0111...1100)
移位运算整体还是比较好理解,左移相当于将数字乘以2的移位数次方。右移相当于将数字除以2的移位数次方,向下取整。
与运算(&)
它对两个整数的二进制位进行操作,只有在两个对应的二进制位都为1时,结果才为1;否则结果为0。
假设有两个整数 a
和 b
:
a = 5
(二进制表示为0101
)b = 3
(二进制表示为0011
)
进行与运算:
0101 (5)
& 0011 (3)
--------
0001 (1)
通过与运算提取特定的位。也可以用来判断某些标志位是否被设置。
判断奇偶
java
isOdd(int a){
return 1 == (a&1);
}
将要判断的数与1进行取与操作,只要结果为1则判断当前数为奇数。因为如果结果为1,则证明当前数二进制最后一位为1,是个奇数。
权限控制
权限控制这里可以结合linux文件系统的三个权限,rwx 即 读(1),写(2),执行(4)。
linux 可以使用chmod 来给用户赋予权限 ,7代表所有权限,5代表读和执行权限,大致可以根据以下与运算来判断是否有某个权限:
java
int READ = 1; // 0001
int WRITE = 2; // 0010
int EXECUTE = 4; // 0100
int userPermissions = 5; // 0101 (具有读和执行权限)
boolean canRead = (userPermissions & READ) != 0; // 检查读权限
boolean canWrite = (userPermissions & WRITE) != 0; // 检查写权限
System.out.println("Can read: " + canRead); // 输出: Can read: true
System.out.println("Can write: " + canWrite); // 输出: Can write: false
清零特定位
如数字15(1111),要将其第二位清理,只需15 & (1011=11)即可。
掩码计算
计算机网络中,有子网掩码,子网掩码 & IP地址就可以计算处当前计算机所处的网络。
例如局域网中,通常子网掩码255.255.255.0,假设某个IP地址为192.168.10.111则将两者进行与运算后结果为192.168.10.0为当前ip所处的网络。
255的二进制是 11111111与任何一个8位以内的整数进行与运算都等于该数的本身。
相反0的二进制是0,与任何一个8位以内的二进制整数进行与运算结果都是0。
或运算(|)
或运算对两个二进制数的每一位进行比较,只要有一个位为 1,结果位就为 1;如果两个位都为 0,结果位为 0。
java
int a = 10; // 二进制: 1010
int b = 12; // 二进制: 1100
int result = a | b; // 结果: 1110 (十进制: 14)
System.out.println(result); // 输出: 14
或运算可以用来进行一些状态标识。在游戏开发中,可以使用或运算来表示角色的状态。例如,角色可以同时处于多个状态,如"跳跃"、"攻击"等。
JUMPING = 1 # 0001
ATTACKING = 2 # 0010
DEFENDING = 4 # 0100
# 角色当前状态为跳跃和攻击
character_state = JUMPING | ATTACKING # 结果: 0011
# 检查角色是否在攻击
is_attacking = character_state & ATTACKING != 0 # 结果为 True
异或运算(^)
异或运算当两个对应的二进制位不相同,结果为1;相同则为0。
- 0^0 = 0
- 0 ^ 1 = 1
- 1^ 0 = 1
- 1 ^ 1 = 0
java
int a = 10; // 二进制: 1010
int b = 12; // 二进制: 1100
int result = a ^ b; // 结果: 0110 (十进制: 6)
System.out.println(result); // 输出: 6
异或运算有以下特点:
a^a = 0 :一个数与自己进行异或结果为0。
a^0=a : 以数与0进行异或操作结果为其本身。
a^b^b=a:对一个值进行两次异或运算使用同一个数,可以恢复到原始值。这使得异或运算在加密和解密中非常有用。因为这样运算是可逆的。
异或运算与运算的顺序无关,a^b^b=a 等价于 a^(b^b) = a ^0=a
使用异或运算进行两个数值交换
java
int a = 5;
int b = 3;
a = a^b;
b = a^b; //a = a^b 代入 b = a^b^b = a 现在b=a = 3
a = a^b; //此时b=a ,a= a^b 带入 a^b^a = b
System.out.println(a+" "+b);
找出数组中只出现一次的元素
java
int[] arr = {5,2,3,2,4,5,4};
int result = 0;
for (int i : arr) {
result ^= i;
}
System.out.println(result);
这里使用了异或运算的对一个值进行两次相同异或运算等于同一个数
取反运算(~)
取反运算将每个二进制位取反,0变成1,1变成0。
java
int a = 8;//0000 1000
/**
取反后 1111 0111
1111 0111 是负数的补码表示形式,要得到其对应的十进制数,我们需要将其转换为正数。
负数的补码是将其绝对值的二进制表示取反后加 1,所有最后结果是-9
*/
System.out.println(~a);
位运算总结下如下表格:
A B A|B A&B A^B ~A
0 0 0 0 0 1
1 0 1 0 1 0
0 1 1 0 1 1
1 1 1 1 0 0