在计算机科学中,位运算是一种直接对二进制进行操作的技术。由于位运算的执行速度快且资源消耗低,它在多种场景中有着广泛的应用,特别是在性能优化、权限控制、数据压缩、网络编程和加密安全等领域有不可替代的作用。
Hello,兄弟们,我就是风流倜傥、文武双全、人见人爱、车见车载、花见花开、老公鸡见了会下蛋、老母猪见了会上树、改善社会风气、风靡万千少女、提高青少年内涵、一支梨花压海棠,深受大家喜爱的阿玮geigei。
下面我们就正式开始!
1. 位运算基础
位运算符是直接作用于二进制位上的操作符。我们先来看一下他的基本运算逻辑,再看他在实际应用中有什么骚操作。
常见的位运算符(英文名) | 中文名/英文名 | 计算规则 |
---|---|---|
& | 按位与(AND) | 两位都为1,结果就为1,否则结果为0 |
` | ` | 按位或(OR) |
^ | 按位异或(XOR) | 相同为1,不同为0 |
~ | 按位取反(NOT) | 0变1,1变0 |
<< | 左移(LEFT SHIFT) | 符号位不变,低位补0 |
>> | 带符号右移(RIGHT SHIFT) | 低位溢出(舍弃),符号位不变,用符号位补溢出的高位 |
>>> | 无符号右移(UNSIGNED RIGHT SHIFT) | 低位溢出(舍弃),高位补0 |
注:计算规则中,1可以理解为true 0可以理解为false
2. 位运算的计算过程
2.1 补码的计算方式
位运算底层都是以补码的形式进行计算的,所以,我们要先把数字的补码计算出来,补码的计算规则如下:
- 正数和0:直接转成二进制即可,正数和0的原码、反码、补码都是一样的
- 负数:需要按照以下规则进行计算
- 原码:十进制直接转成的二进制
- 反码:符号位不变,原码按位取反
- 补码:反码 + 1
让我们通过一个简单的例子来说明补码是如何计算的。
正数3
原码:0000 0011
反码:0000 0011
补码:0000 0011
负数3
原码:1000 0011 (最左边的二进制是符号位,0为正,1为负)
反码:1111 1100
补码:1111 1101
2.2 位运算的计算过程
知道了如何计算补码,接下来我们通过一个简单的例子来说明这些位运算符在底层是如何工作的。
假设我们有两个整数 a = 5
(二进制: 0000 0101
) 和 b = 3
(二进制: 0000 0011
)。
练习一:按位与(AND)
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int b = 3; // 0000 0000 0000 0000 0000 0000 0000 0011
int c = a & b; // 结果:0000 0000 0000 0000 0000 0000 0001
// 计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
//& 0000 0000 0000 0000 0000 0000 0000 0011 // &的计算规则:两位都为1,结果就为1,否则结果为0
-----------------
// 0000 0000 0000 0000 0000 0000 0000 0001 // 转成十进制为:1
练习二:按位或(OR)
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int b = 3; // 0000 0000 0000 0000 0000 0000 0000 0011
int c = a | b; // 结果:0000 0111
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
//| 0000 0000 0000 0000 0000 0000 0000 0011 // |的计算规则:两位只要有一个为1,结果就为1,否则结果为0
-----------------
// 0000 0000 0000 0000 0000 0000 0000 0111 // 转成十进制为:7
练习三:按位异或(XOR)
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int b = 3; // 0000 0000 0000 0000 0000 0000 0000 0011
int c = a ^ b; // 结果:0000 0110
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
//^ 0000 0000 0000 0000 0000 0000 0000 0011 // ^的计算规则:相同为1,不同为0
-----------------
// 0000 0110 // 转成十进制为:7
练习四:按位取反(NOT)
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int c = ~a; // 结果:1111 1010
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
// ~取反
-----------------
// 1111 1111 1111 1111 1111 1111 1111 1010 // 转成十进制为:-6
练习五:左移(LEFT SHIFT)
简单理解:左移一位就是乘2,左移两位就是乘两次2
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int c = a << 2;// 结果:0000 0000 0000 0000 0000 0000 0001 0100
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
// 左移两位,就是把上面的二进制数据向左移动两位,符号位不变,低位补0
-----------------
// 0000 0000 0000 0000 0000 0000 0001 0100 // 转成十进制为:20
练习六:带符号右移(RIGHT SHIFT)
简单理解:右移一位就是除以2,右移两位就是除以两次2
正数:
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int c = a >> 2;// 结果:0000 0000 0000 0000 0000 0000 0000 0001
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
// 右移两位,就是把上面的二进制数据向右移动两位,低位溢出(舍弃),符号位不变,用符号位补溢出的高位
-----------------
// 0000 0000 0000 0000 0000 0000 0000 0001 // 转成十进制为:1
负数:
int a = -10;// 0000 0000 0000 0000 0000 0000 1111 0110
int c = a >> 2;// 结果:0000 0000 0000 0000 0000 0000 1111 1101
//计算过程:
// 0000 0000 0000 0000 0000 0000 1111 0110
// 右移两位,就是把上面的二进制数据向右移动两位,低位溢出(舍弃),符号位不变,用符号位补溢出的高位
-----------------
// 1111 1111 1111 1111 1111 1111 1111 1101 // 转成十进制为:-3
练习七:无符号右移(UNSIGNED RIGHT SHIFT)
正数:
int a = 5; // 0000 0000 0000 0000 0000 0000 0000 0101
int c = a >>> 2;// 结果:0000 0000 0000 0000 0000 0000 0000 0001
//计算过程:
// 0000 0000 0000 0000 0000 0000 0000 0101
// 右移两位,就是把上面的二进制数据向右移动两位,低位溢出(舍弃),高位补0
-----------------
// 0000 0000 0000 0000 0000 0000 0000 0001 // 转成十进制为:1
负数:
int a = -10;// 1111 1111 1111 1111 1111 1111 1111 0110
int c = a >>> 2;// 结果:0011 1111 1111 1111 1111 1111 1111 1101
//计算过程:
// 1111 1111 1111 1111 1111 1111 1111 0110
// 右移两位,就是把上面的二进制数据向右移动两位,低位溢出(舍弃),符号位不变,用符号位补溢出的高位
-----------------
// 0011 1111 1111 1111 1111 1111 1111 1101 // 转成十进制为:1073741821
基础的运算规则就到这里,接下来咱哥们一起来瞅一眼他们在实际开发中到底有什么作用吧!
3. 应用场景
3.1 性能优化
在算法中使用位运算可以显著减少循环次数,从而提高性能。
案例1: 乘以或者除以2的幂,就可以通过左移或者右移快速实现
需求:任意整数除以4
常规做法:
int a = 10;
int b = 4;
int c = a / b;
位运算:
int a = 10;
int b = a >> 2;
对比: 因为计算机中只有加法,而其他的减法、乘法、除法都是层层转换转成加法再进行计算
位运算计算的时候,不会进行转换,直接在二进制的基础上直接移动即可,提高了运算的效率
案例2: 计算一个数是否为2的幂次
常规做法: 利用循环或者递归进行
// 利用循环检测是否为2的幂
public static boolean isPowerOfTwo(int n) {
if (n <= 0) {
return false;
}
while (n > 1) {
if (n % 2 != 0) {
return false;
}
n /= 2;
}
return true;
}
// 利用递归检测是否为2的幂
public static boolean isPowerOfTwoRecursive(int n) {
if (n <= 0) {
return false;
}
if (n == 1) {
return true;
}
return (n % 2 == 0) && isPowerOfTwoRecursive(n / 2);
}
位运算:
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
对比: 传统的做法不管是循环还是递归都需要检查每一位,效率较慢,而位运算只需一步即可得出结果。
震惊吧兄弟们,不仅仅如此,还有更多的应用场景呢!我们慢慢往下看!
3.2 权限控制
案例: 在用户权限管理中,位运算可以用于表示和管理用户的权限集。
// 权限定义
int read = 0b0001;
int write = 0b0010;
int execute = 0b0100;
// 用户权限
int userPermissions = 0b0101;
// 检查权限
if ((userPermissions & read) != 0) {
System.out.println("用户有读权限");
}
if ((userPermissions & write) != 0) {
System.out.println("用户有写权限");
}
if ((userPermissions & execute) != 0) {
System.out.println("用户有执行权限");
}
对比: 传统方法可能需要多个布尔变量或枚举来表示权限,而位运算可以更紧凑地存储和操作这些权限。
3.3 数据压缩
案例: 位运算可以用于实现简单的数据压缩,
例如,将三个布尔值(true,false,true)压缩到单个整数中,压缩之后结果为:101
int bitField = 0;
// 压缩数据
bitField |= 1 << 0; // 设置第1位
bitField |= 1 << 2; // 设置第3位
// 解析数据
if ((bitField & (1 << 0)) != 0) {
System.out.println("第1位被设置");
}
if ((bitField & (1 << 2)) != 0) {
System.out.println("第3位被设置");
}
对比: 直接使用数组或列表存储布尔值会占用更多的内存空间。
说明: 数据压缩除了能压缩布尔值以外,其他任何数据都可以压缩,在文件存储,网络传输,内存管理等地方数据压缩技术大量的被使用
3.4 网络编程
案例: 在网络通信中,IP地址和子网掩码常通过位运算进行处理。
int ipAddress = 0x7F000001; // 127.0.0.1
// 利用位运算快速获取IP中的每一段
int octet1 = (ipAddress >> 24) & 0xFF;
int octet2 = (ipAddress >> 16) & 0xFF;
int octet3 = (ipAddress >> 8) & 0xFF;
int octet4 = ipAddress & 0xFF;
System.out.println(octet1 + "." + octet2 + "." + octet3 + "." + octet4);
对比: 传统方法可能需要字符串拼接和复杂的逻辑判断,而位运算可以简化处理过程。
3.5 加密与安全
案例: 在密码学中,位运算可以用于生成哈希值或密钥。
public class SimpleHash {
public static int hash(String key) {
int h = 0;
for (char c : key.toCharArray()) {
h = (h << 4) ^ (h >>> 28) ^ c;
}
return h;
}
}
对比: 传统方法可能使用循环和数学运算,而位运算可以提供更高效的实现。
嘿嘿,兄弟们,虽然有一丢丢的小震惊,但是有没有一种感觉,位运算在实际应用中嘎嘎牛逼呢?
就一个字:“高端”!
总结:
位运算提供了强大的工具,可以帮助开发者在多种场景中优化代码性能、减少内存使用、简化逻辑处理。通过上述案例,我们可以看到位运算在实际应用中的优势。虽然位运算的强大,但在使用时也需要注意其可读性和维护性,确保代码易于理解和调试。
兄弟们,有没有觉得自己奇奇怪怪的知识又增加了呢?
想要学习更多有趣的知识吗?想要直面大咖老师解决你学习、工作上的疑惑吗?机会来了,加入我们的知识星球吧!
这是黑马程序员专门给广大IT自学爱好者推出的一个学习交流社区-《黑马智学伴侣》。
在【黑马智学伴侣】里,不单单有大帅比阿玮,还有其他更多大咖老师的分享,以及更多Java + AI的完整学习路线以及更多的服务,包括但不限于:
- 明确学习路径:为你规划专属学习路线,让每一步都清晰可见,不走弯路,直达目标。
- 大咖名师指导:行业名师大咖齐聚一堂,为你答疑解惑,助你快速入门,深入学习。
- 实时行业动态:紧跟科技前沿,为你解读最新行业动态,让你的职业规划更加清晰明确。
- 智能学习评估:AI 辅助学习,智能检验你的学习效果,让你随时了解自己的学习进度。
- 实战项目经验:每月项目上新,贴合企业需求,让你在实战中提升技能,增强竞争力。
- 社群交流互动:与志同道合的伙伴们一起学习、交流,建立人脉网络,共同成长。
- 行业顶尖资源:汇聚了行业顶尖资源,为你提供实时动态解读,助你轻松进入IT行业。
在「黑马智学伴侣」知识星球,我们不仅是师生、伙伴,更是并肩追梦的战友!无论你处于哪个学习阶段,我们都会全程陪伴,见证你的成长与蜕变。在这里,你永远不会孤单,因为我们始终与你同行!
现在,只需轻轻一扫下方二维码,或点击下方链接,即可立刻加入「黑马智学伴侣」知识星球,享受我们为您提供的专属服务:
- 点击链接,轻松加入知识星球:https://t.zsxq.com/bp1I9
- 或扫描下方二维码,即刻成为知识星球的一员: