Java中的位操作以及位操作技巧

在C语言学习中位操作是单独拿出来讲的一章,在Java实践中相当少机会使用。

位操作基础

基本的位操作符有与、或、异或、取反、左移、右移这6种,它们的运算规则如下所示:

  1. & 与运算符、 | 或运算符 、~ 取反运算符 、^ 异或运算符、>> 右移运算符、<< 左移运算符、>>>无符号右移运算符

  2. 在这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符。
  3. 位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。
  4. 位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成int a = 1<<i + 1;是不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 <<i) + 1;
  5. 另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>=。

原码 反码 补码

一个 int 型数值是 4 个字节。每个字节有 8 位。但对于一个 int 或者其它整数类型如 (long)的数值而言还要注意的是,它的最高位是符号位。

  • 最高位为0表示正数。
  • 最高位为1表示负数

原码 将一个数字转换成二进制就是这个数值的原码。

int a = 5; //原码 0000 0000 0000 0101
int b = -3; //原码 1000 0000 0000 0011

反码 
分两种情况:正数和负数

  • 正数 正数的反码就是原码。
  • 负数 负数的反码是在原码的基础上,符号位不变,其它位都取反。

5 的原码:0000 0000 0000 0101
5 的反码:0000 0000 0000 0101
-3 的原码:1000 0000 0000 0011
-3 的反码:1111 1111 1111 1100

补码 
仍然分正数和负数两种情况

  • 正数 正数的补码就是原码。
  • 负数 负数的补码在反码的基础上加1。

5 的原码:0000 0000 0000 0101
5 的补码:0000 0000 0000 0101
-3 的反码:1111 1111 1111 1100
-3 的补码:1111 1111 1111 1101

现在有一个问题,一周中星期四的前三天是星期几?我们可以把一周7天用1-7来表示,那么星期四的前三天可以表示为 4 -3 = 1 ,那么星期四之后四天还是星期一,那么我们可以表示成 4+4 = 1,为什么有4+4 = 1,因为4+4 = 8,大于7之后又是一个新的循环,所以实际上应该写成 (4+4)%7 = 1,所以可以看到-3和4 是补数

在计算机中,为什么不用原码和反码,而是用补码呢?

因为在使用原码、反码在计算时不准确,使用补码计算时才准确。补码可以简化加减法运算,而符号位也可以直接参与运算,不必单独计算符号位。计算机中二进制数值是以补码的形式存储。

1>.使用原码计算10-10

         0000 1010  (10的原码)

    +     1000 1010   (-10的原码)

------------------------------------------------------------

         1001 0100  (结果为:-20,很显然按照原码计算答案是否定的。)

2>.使用反码计算10-10

      0000 1010  (10的反码)

    +  1111 0101  (-10的反码)

------------------------------------------------------------

      1111 1111  (计算的结果为反码,我们转换为原码的结果为:1000 0000,最终的结果为:-0,很显然按照反码计算答案也是否定的。)

3>.使用补码计算10-10

      0000 1010  (10的补码)

   +   1111 0110  (-10的补码)

------------------------------------------------------------

      1 0000 0000  (由于我们这里使用了的1个字节存储,因此只能存储8位,最高位(第九位)那个1没有地方存,就被丢弃了。因此,结果为:0)

计算过程:0b1111 1111(补码)------>0b1111 1110(反码)------>0b1000 0001(原码)

计算思路:5-3 = 5+(-3) =  2

计算值原码补码
50000 0000 0000 0101   0000 0000 0000 0101
-31000 0000 0000 0011   1111  1111  1111  1101
结果 1 0000 0000 0000 0010

位运算

& 与运算符

规则 与运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值都是 1,则结果值相应的 bit 就是 1,否则为 0。

0000 0011
&
0000 0101
=
0000 0001

| 或运算符

规则 与运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值只要 1 个为 1,则结果值相应的 bit 就是 1,否则为 0。

0000 0011
|
0000 0101
=
0000 0111

~ 取反运算符

规则 对操作数的每一位进行操作,1 变成 0,0 变成 1。

~5 =>  0000 0101   ~  => 1111 1010

^ 异或运算符

规则 两个操作数进行异或时,对于同一位上,如果数值相同则为 0,数值不同则为 1。

0000 0011
^
0000 0101
=
0000 0110

异或运算可以作为简单的加解密运算算法。

满足一下性质:

  1. 交换律
  2. 结合律(即(a^b)^c == a^(b^c))
  3. 对于任何数x,都有x^x=0,x^0=x
  4. 自反性 A XOR B XOR B = A XOR 0 = A

>> 右移运算符

规则 a >> b 将数值 a 的二进制数值(补码)从 0 位算起到第 b - 1 位,整体向右方向移动 b 位,正数左补0,负数左补1,右边丢弃。

5 >> 1        0000 0000 0000 0101 >> 1  = 0000 0000 0000 0010 = 2
-7 >> 2       1111 1111 1111 1001 >> 2  = 1111 1111 1111 1110 = -2

a >> b = a / ( 2 ^ b )   所以 5 >> 1= 5 / 2 = 2;11 >> 2 = 11 / 4 = 2。

 << 左移运算符

规则 a << b 将数值 a 的二进制数值(补码)从 0 位算起到第 b - 1 位,整体向左方向移动 b 位,低位空出来的位补数值 0。

5 << 1      0000 0000 0000 0101 << 1  = 0000 0000 0000 1010 = 10
-7 << 2     1111 1111 1111 1001 << 2  = 1111 1111 1110 0100 = -28

 a << b = a * (2 ^ b)

如果某个数值右移 n 位,就相当于拿这个数值去除以 2 的 n 次幂。如果某个数值左移 n 位,就相当于这个数值乘以 2 ^ n。

>>>无符号右移运算符

运算规则:

  • 按照操作符右侧指定的位数将操作符左边的操作数向右移动(0扩展机制);
  • 移位过程中,无论数值是正数还是负数,都在最高位补0
  • 只对32位和64位值有意义
-1>>>1  11111111 11111111 11111111 11111111>>>1 = 01111111 11111111 11111111 11111111 = 2147483647

常用位操作小技巧

toBinaryString 方法

System.out.println(Integer.toBinaryString(3)); // 输出:11

System.out.println(Integer.toBinaryString(-248)); // 输出:11111111111111111111111100001000

含义:返回参数数值的补码形式,正数则忽略前面的0。(官方注释:返回表示传入参数的一个无符号(这里无符号大概只是指前面没有+-号,但还是有符号位) 的二进制字符串。如果参数为负数x,返回值为 2^32 + x 【就是它的补码】)

交换两数

int c = 1, d = 2;

c ^= d; //cd异或
d ^= c; //异或结果与异或运算因子异或
c ^= d;

System.out.println("c=" + c);
System.out.println("d=" + d);

变换符号

变换符号就是正数变成负数,负数变成正数。
1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)

int a = -15, b = 15;

System.out.println(~a + 1);
System.out.println(~b + 1);

求绝对值

int j = a >> 31;
System.out.println((a ^ j) - j);

位操作技巧

// 1. 获得int型最大值
System.out.println((1 << 31) - 1);// 2147483647, 由于优先级关系,括号不可省略
System.out.println(~(1 << 31));// 2147483647

// 2. 获得int型最小值
System.out.println(1 << 31);  //
System.out.println(1 << -1);

// 3. 获得long类型的最大值
System.out.println(((long)1 << 127) - 1);
最小值
System.out.println((long)1 << 127);

// 4. 乘以2运算
System.out.println(10<<1);

// 5. 除以2运算(负奇数的运算不可用)
System.out.println(10>>1);

// 6. 乘以2的m次方
System.out.println(10<<2);

// 7. 除以2的m次方
System.out.println(16>>2);

// 8. 判断一个数的奇偶性
System.out.println((10 & 1) == 1);
System.out.println((9 & 1) == 1);

// 9. 不用临时变量交换两个数(面试常考)
a ^= b;
b ^= a;
a ^= b;

// 10. 取绝对值(某些机器上,效率比n>0 ? n:-n 高)
int n = -1;
System.out.println((n ^ (n >> 31)) - (n >> 31));

/* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1
若n为正数 n^0-0数不变,若n为负数n^-1 需要计算n和-1的补码,异或后再取补码,
结果n变号并且绝对值减1,再减去-1就是绝对值 */

// 11. 取两个数的最大值(某些机器上,效率比a>b ? a:b高)
System.out.println(b&((a-b)>>31) | a&(~(a-b)>>31));

// 12. 取两个数的最小值(某些机器上,效率比a>b ? b:a高)
System.out.println(a&((a-b)>>31) | b&(~(a-b)>>31));

// 13. 判断符号是否相同(true 表示 x和y有相同的符号, false表示x,y有相反的符号。)
System.out.println((a ^ b) > 0);

// 14. 计算2的n次方 n > 0
System.out.println(2<<(n-1));

// 15. 判断一个数n是不是2的幂
System.out.println((n & (n - 1)) == 0);

/*如果是2的幂,n一定是100... n-1就是1111....所以做与运算结果为0*/
判断2的幂次方:(val & -val) == val /*反码以后补码加1,只是符号位不一致*/

// 16. 求两个整数的平均值
System.out.println((a+b) >> 1);

// 17. 从低位到高位,取n的第m位
int m = 2;
System.out.println((n >> (m-1)) & 1);

// 18. 从低位到高位.将n的第m位置为1
System.out.println(n | (1<<(m-1)));
/*将1左移m-1位找到第m位,得到000...1...000
n在和这个数做或运算*/

// 19. 从低位到高位,将n的第m位置为0
System.out.println(n & ~(0<<(m-1)));
/* 将1左移m-1位找到第m位,取反后变成111...0...1111
n再和这个数做与运算*/

 

参考:

Java基础-原码反码补码

可能是最通俗易懂的 Java 位操作运算讲解

详解Java中的位操作

从java toBinaryString() 看计算机数值存储方式(原码、反码、补码)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值