Java中的位运算

Java中支持的位运算

  1. 位与(&):二元运算符,两个为1时结果为1,否则为0
  2. 位或(|):二元运算符,两个其中有一个为1时结果就为1,否则为0
  3. 位异或(^):二元运算符,两个数同时为1或0时结果为1,否则为0
  4. 位取非(~):一元运算符,取反操作,即1变为0,0变为1
  5. 左移(<<):一元运算符,按位左移一定的位置。高位溢出,低位补0,符号位不变。
  6. 右移(>>):一元运算符,按位右移一定的位置。高位补符号位,符号位不变,低位溢出。
  7. 无符号右移(>>>):一元运算符,高位补零,低位溢出。

位运算规则

计算机对于计算数据的话,都是以二进制来进行运算的,所以使用位运算相比直接使用(+、-、 *、/)运算符,要更高效,能显著调高代码在计算机中的执行效率。

对于有符号的而言,

  • 最高位为符号位,0表示正数,1表示负数
  • 正数的原码,反码和补码都一样,三码合一
  • 负数的反码:符号位保持不限,其他位取反
  • 负数的补码:反码 + 1
  • 0的反码和补码都是0
  • 数据存储和运算都是以补码的方式进行的

下面以 -1 为例子展示原码、反码和补码的转换关系(以int数据类型为例,int类型在Java中占4字节):

在这里插入图片描述

逻辑运算

与运算(&)

两个数相同位置的比特进行与运算,若两个位置均为1,那么结果就为1,否则为0

计算 a = 4 & -5

​ 因为4为正数,所以原码和补码相同,即4的补码为:00000000 0000000 00000000 00000100

因为-5为负数,所以需要进行原码 -> 反码 -> 补码的转换。转换步骤如下:

1)原码:10000000 00000000 00000000 00000101

2)反码:11111111 11111111 11111111 11111010

3)补码:11111111 11111111 11111111 11111011

将4和-5的补码进行 & 运算得到补码结果:00000000 0000000 00000000 00000000

所以最终结果:a = 0(0的反码和补码都是0)

或运算(|)

两个数相同位置的比特进行或运算,若两个位置其中一个为1则结果为1,否则为0

计算 a = -2 | 5

因为-2为负数,所以需要进行原码 -> 反码 -> 补码的转换。转换步骤如下:

1)原码:10000000 00000000 00000000 00000010

2)反码:11111111 11111111 11111111 11111101

3)补码:11111111 11111111 11111111 11111110

因为5为正数,补码和反码一致,所以5的补码为:00000000 00000000 00000000 00000101

将-2 和 5 的补码进行 | 运算得到补码结果:11111111 11111111 11111111 11111111

结果显然是一个负数,而负数的补码和原码不一致,所以需要将补码结果转换为原码才能得到最终的结果

补码转原码的过程是跟原码转补码相反的过程,具体过程如下:

1)补码:11111111 11111111 11111111 11111111

2)补码 -1 得到反码:11111111 11111111 11111111 11111110

3)符号位不变,其他位置取反得原码:10000000 00000000 00000000 00000001

所以最终结果:a = -1

异或运算(^)

两个数相同位置的比特进行异或运算,若两个数均为0或1,则结果为0,否则为1

计算 a = 1 ^ -5

​ 1的补码为:00000000 00000000 00000000 00000001

-5的补码为:11111111 11111111 11111111 11111011

两个补码进行 ^ 运算得到补码结果:11111111 11111111 11111111 11111010

补码为负数,需要转换成原码:

1)补码:11111111 11111111 11111111 11111010

2)反码:11111111 11111111 11111111 11111001

3)原码:10000000 00000000 00000000 00000110

​ 所以最终结果:a = -6

取反运算(~)

若位数为0,则取反后为1,若为1,取反后为0

计算 a = ~2

​ 2的原码为:00000000 00000000 00000000 00000010

2的补码为:00000000 00000000 00000000 00000010

取反:11111111 11111111 11111111 11111101

取反后的结果仍为补码,此时补码为负数,则需要转成原码

​ -1 得到反码:11111111 11111111 11111111 11111100

​ 符号位不变,其他位置取反得到原码为:10000000 00000000 00000000 00000011

​ 所以最终结果:a = -3

位移操作

Java中的位移操作只针对int类型的有效,Java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数。也可作用于以下类型,即 byte,short,char,long(它们都是整数形式)。当为这四种类型时,JVM先把它们转换成int类型再进行操作。

左移(<<)

高位溢出,低位补0,符号位不变。移动位数超过该类型的最大位数,则进行取模,如对Integer型左移34位,实际上只移动了两位。左移一位相当于乘以2的一次方,左移n位相当于乘以2的n次方。

正数 a = 20 << 2

​ 20的二进制补码:00000000 00000000 00000000 00010100

​ 向左移动两位后: 00000000 00000000 00000000 01010000

​ 结果: a = 80

负数 a = -20 << 2

​ -20的二进制原码:10000000 00000000 00000000 00010100

​ -20的二进制反码:11111111 11111111 11111111 11101011

​ -20的二进制补码:11111111 11111111 11111111 11101100

​ 左移两位后的补码:11111111 11111111 11111111 10110000

​ 反码:11111111 11111111 11111111 10101111

​ 原码:10000000 00000000 00000000 01010000

​ 结果:a = -80

右移(>>)

高位补符号位,符号位不变,低位溢出右移一位相当于除以2的一次方,右移n位相当于除以2的n次方。除不尽向下取整。

正数:a = 20 >> 2

20的二进制补码:00000000 00000000 00000000 00010100

向右移动两位后:00000000 00000000 00000000 00000101

结果:a = 5

负数:r = -20 >> 2

-20 的二进制原码 :10000000 00000000 00000000 00010100

-20 的二进制反码 :11111111 11111111 11111111 11101011

-20 的二进制补码 :11111111 11111111 11111111 11101100

右移两位后的补码:11111111 11111111 11111111 11111011

反码:11111111 11111111 11111111 11111010

原码:10000000 00000000 00000000 00000101

结果:r = -5

无符号右移(>>>)

高位补零,低位溢出。即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

正数:a = 20 >>> 2 与 a = 20 >> 2相同

​ 结果:a = 5

负数 a = -20 >>> 2

​ -20的二进制原码:10000000 00000000 00000000 00010100

​ -20的二进制反码:11111111 11111111 11111111 11101011

​ -20的二进制补码:11111111 11111111 11111111 11101100

​ 右移两位后的补码:00111111 11111111 11111111 11111011 (正数的原码、反码、补码都是一样)

​ 反码:00111111 11111111 11111111 11111011

​ 原码:00111111 11111111 11111111 11111011

​ 结果:a = 1073741819

位运算的使用奇巧淫技

判断奇偶数

int num = 5;
System.out.println(num + "是" + ((num&1) == 0 ? "偶数":"奇数"));

5的二进制代码为101,1的二进制代码为001,5 & 1 就是 101 & 001

我们通过二进制判断奇偶数的话,看的是二进制最后一位,如果最后一位为0的话是偶数,为1的话是奇数

为什么?因为二进制除了最后一位,其他位都是2的幂次方,必然是偶数,所以我们通过判断最后一位是0或者1 就可以判断是奇数还是偶数。

获取二进制位是1还是0

int n = 86;
System.out.println(n + "的第五位是" + (((n&(1<<4))>>4) == 0 ? "0":"1"));
System.out.println(n + "的第五位是" + (((n>>4)&1) == 0 ? "0":"1"));

交换两个整数变量的值

int num1 = 10;
int num2 = 20;
num1 = num1^num2;
num2 = num1^num2;
num1 = num1^num2;
System.out.println("num1=" + num1 + ", num2=" + num2);

异或运算满足的性质:

  1. 可交换性:ab=ba
  2. 可结合性:abc=(ab)c=a(bc)
  3. 自身进行异或运算值为零:a^a=0
  4. 与 0 异或时结果不变:a^0=a

num2可以看成是num1^(num2^num2),故结果为num1

num1可以看成是(num1^num1)^(num2^num2)^num2,故结果为num2

不用判断语句,求整数的绝对值

int a = -10;
int b = ((a >> 31) ^ a) + (a >>> 31);
System.out.println("a=" + a + ", b=" + b);

先来分析一下 a = -86:

原码:10000000 00000000 00000000 01010110

反码:11111111 11111111 11111111 10101001

补码:11111111 11111111 11111111 10101010

取反:00000000 00000000 00000000 01010101

(+1): 00000000 00000000 00000000 01010110

由此可以得出,负数的绝对值为,补码取反+1,即 ~a+1。

int a = -86;
int b = ~a + 1;
System.out.println(b);

但是,这个还不能达到题目所要求的,不用判断,当假定 a = -86的时候,就已经知到 a 是负数了。

分析:任何一个数 异或0 结果为其本身,异或-1 (二进制全为1) 相当于取反

而一个32 bit的int类型的正数 >> 31,结果为0,负数 >> 31 为-1。

即 (a >> 31) 的结果为 0 或者 -1。如果 a 为负数,(a >> 31) ^ a 相当于 ~a,则需要 + 1 才能得到 a 的绝对值

且一个32 bit的int类型的正数 >>> 31,结果为0,负数 >>> 31为1。

所以代码为:((a >> 31) ^ a) + (a >>> 31)

也可以写成:

int a = -86;
int b = a >> 31;
// 当a为正时:b = 0,a ^ b = a;
// 当a为负时:b = -1,a ^ b = ~a;
return (a ^ b) - b; // 也可以写为:(a ^ (a >> 31)) - (a >> 31)

常见面试题

Q:2*8怎么运算性能最好

A:我当时遇到这个面试题的时候是懵的,直接回答两个new BigDecimal();再进行运算就行了。实际采用位运算的方式是性能最好的,因为计算机对于计算数据的话,都是以二进制来进行运算的,所以使用位运算相比直接使用(+、-、 *、/)运算符,要更高效,能显著调高代码在计算机中的执行效率。相当于2 << 3


Q:怎么交换两个整数变量的值

A:我当时遇到这个面试题的时候也是懵的,不知道这题的考点是啥,直接回答:创建一个临时变量,把其中一个变量赋值给临时变量,然后再把另一个变量赋值给这个变量,最后把临时变量赋值给另一个变量。实际直接采用异或运算即可,具体可见奇巧淫技中的交换两个整数变量的值

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值