深入理解Java中的位运算
目录
一、位运算基础:认识二进制世界的操作符
1. 按位与(&)
2. 按位或(|)
3. 按位异或(^)
4. 按位非(~)
5. 左移(<<)
6. 右移(>>)
7. 无符号右移(>>>)
二、位运算的经典应用场景
1. 权限控制:用位掩码实现多权限管理
2. 数值交换:无需临时变量的高效交换
3. 奇偶判断:比取模运算更高效
4. 求绝对值:避免分支判断
5. 位计数:统计二进制中1的个数
三、位运算的注意事项
四、总结
位运算作为计算机最底层的运算方式,直接操作二进制位,具有极高的执行效率。在Java中,位运算虽然不常出现在业务代码中,但在框架设计、算法优化、数据压缩等场景中却有着不可替代的作用。
一、位运算基础:认识二进制世界的操作符
Java提供了7种位运算符,均作用于整数类型(byte、short、int、long),直接对二进制位进行操作。在了解具体运算符前,我们需要明确:Java中数值以补码形式存储,正数的补码与原码相同,负数的补码是其原码取反加1。
1. 按位与(&)
- 运算规则:两个位都为1时,结果才为1,否则为0
- 数学意义:可理解为"同时满足"的逻辑
- 示例:
int a = 5; // 二进制:0000 0101
int b = 3; // 二进制:0000 0011
int c = a & b; // 结果:0000 0001 → 十进制1
2. 按位或(|)
- 运算规则:两个位只要有一个为1,结果就为1
- 数学意义:可理解为"至少满足一个"的逻辑
- 示例:
int a = 5; // 0000 0101
int b = 3; // 0000 0011
int c = a | b; // 0000 0111 → 十进制7
3. 按位异或(^)
- 运算规则:两个位不同时结果为1,相同时为0
- 特殊性质:
- 与0异或结果为其本身(a ^ 0 = a)
- 与自身异或结果为0(a ^ a = 0)
- 满足交换律和结合律(a ^ b ^ c = a ^ (b ^ c))
- 示例:
int a = 5; // 0000 0101
int b = 3; // 0000 0011
int c = a ^ b; // 0000 0110 → 十进制6
4. 按位非(~)
- 运算规则:0变1,1变0(一元运算符)
- 注意点:对正数操作会得到负数,因为符号位会被反转
- 示例:
int a = 5; // 0000 0101
int b = ~a; // 1111 1010(补码)→ 十进制-6
5. 左移(<<)
- 运算规则:将二进制位整体左移n位,右侧补0
- 数学意义:相当于乘以2的n次方(不溢出情况下)
- 示例:
int a = 5; // 0000 0101
int b = a << 2; // 0001 0100 → 十进制20(5×2²=20)
6. 右移(>>)
- 运算规则:将二进制位整体右移n位,左侧补符号位(正数补0,负数补1)
- 数学意义:相当于除以2的n次方(向下取整)
- 示例:
int a = 20; // 0001 0100
int b = a >> 2; // 0000 0101 → 十进制5(20÷2²=5)int c = -20; // 补码:1110 1100
int d = c >> 2; // 1111 1011 → 十进制-5
7. 无符号右移(>>>)
- 运算规则:将二进制位整体右移n位,左侧始终补0
- 注意点:仅对32位int和64位long有效,会把负数当作正数处理
- 示例:
int a = -20; // 补码:1110 1100(简化为8位展示)
int b = a >>> 2; // 0011 1011 → 十进制59(无符号处理)
二、位运算的经典应用场景
位运算的高效性(通常是CPU级别的操作)使其在特定场景中大放异彩,以下是几个典型应用:
1. 权限控制:用位掩码实现多权限管理
位运算非常适合实现"开关式"权限控制,每个二进制位代表一种权限状态(1表示拥有,0表示未拥有):
// 定义权限常量(每个常量是2的幂,确保二进制只有一位为1)
public class Permission {public static final int READ = 1 << 0; // 0001 → 1public static final int WRITE = 1 << 1; // 0010 → 2public static final int EXECUTE = 1 << 2; // 0100 → 4public static final int DELETE = 1 << 3; // 1000 → 8// 检查是否拥有指定权限public static boolean hasPermission(int permissions, int target) {return (permissions & target) == target;}// 添加权限public static int addPermission(int permissions, int target) {return permissions | target;}// 移除权限public static int removePermission(int permissions, int target) {return permissions & ~target;}public static void main(String[] args) {int userPerms = READ | WRITE; // 拥有读写权限System.out.println(hasPermission(userPerms, READ)); // trueSystem.out.println(hasPermission(userPerms, EXECUTE)); // falseuserPerms = addPermission(userPerms, EXECUTE); // 增加执行权限userPerms = removePermission(userPerms, WRITE); // 移除写权限}
}
这种方式比使用集合或布尔值数组节省内存,且权限操作(增删查)效率极高。
2. 数值交换:无需临时变量的高效交换
利用异或的特性,可以不借助临时变量实现两个整数的交换:
public static void swap(int[] arr, int i, int j) {if (i != j) { // 避免自身异或导致值变为0arr[i] ^= arr[j]; // a = a ^ barr[j] ^= arr[i]; // b = b ^ (a ^ b) = aarr[i] ^= arr[j]; // a = (a ^ b) ^ a = b}
}
3. 奇偶判断:比取模运算更高效
判断一个数是奇数还是偶数,使用位运算比n % 2
更高效:
// 二进制末位为1则是奇数
public static boolean isOdd(int n) {return (n & 1) == 1;
}
4. 求绝对值:避免分支判断
利用位运算可以实现无分支的绝对值计算(适用于32位int):
public static int abs(int n) {int mask = n >> 31; // 正数mask为0,负数mask为-1(全1)return (n ^ mask) - mask; // 负数时相当于~n + 1(取补码)
}
5. 位计数:统计二进制中1的个数
统计一个数的二进制表示中1的个数(汉明重量):
// Brian Kernighan算法:每次清除最右边的1,直到数变为0
public static int countOneBits(int n) {int count = 0;while (n != 0) {n &= n - 1; // 清除最右边的1count++;}return count;
}
三、位运算的注意事项
1.类型提升问题:对byte、short等类型进行位运算时,会先自动提升为int再运算,需注意结果类型转换
byte a = 0b11111111; // -1(byte类型)
int b = a & 0xFF; // 0x000000FF → 255(正确获取无符号值)
2.移位溢出问题:移位位数超过类型位数时,会进行取模处理(int取模32,long取模64)
int a = 1;
int b = a << 33; // 相当于1 << 1 → 2(33 % 32 = 1)
四、总结
位运算作为直接操作二进制的底层运算方式,在Java中虽然不常使用,但掌握它能让我们:
理解框架和JDK源码中复杂的位操作逻辑(如HashMap中的哈希计算)等
学习位运算的关键在于理解二进制的操作逻辑,多结合实际场景练习。