Java中的位运算

引子

在上一篇文章《计算机信息的表示与存储》中,我详细地总结了原码、补码等表示方式以及其计算。而这些的目的,就是为了引出本文——位运算相关的知识。这里,我将仔细讲一讲位运算相关的知识。

位运算基础

在Java中,位运算操作分为两种:按位操作 和 移位操作。和C/C++相同,Java中也支持 按位非(~)、按位与(&)、按位或( | ) 和 按位异或(^) 四个按位操作,以及 左移(<<) 和 有符号右移(>>) 两个移位操作。此外,Java还额外支持了一种移位操作:无符号右移:>>> 。这是C/C++中所没有的操作。下面我会详细介绍这些操作。

按位操作符

首先看一看 按位操作符 和 逻辑运算操作符的对比:

! :非~ :按位非,也叫做“取反”
&& :与,且& :按位与
|| :或| :按位或
 ^ :按位异或

按位操作符用来操作整数基本数据类型中的一个二进制位(即1个bit)。

在下面的例子中,我都使用1个字节来存放整数。

按位非:~

按位非 ~ 是一个单目运算符,其作用是是二进制数的每一位取反。

例:求 ~25 = ?

首先,分析题目,要求 ~25的值。在计算机中,数值都是以补码的形式存储的。25是个正数,则其原码、反码和补码都一样,很简单就能计算得到 25 的补码为 00011001。然后我们再对其取反:

对其取反后,得到的结果记为R,则R仍然是补码。很明显R为负数,我们再对其求补码,即可得到R对应的原码:

经过计算得出,R对应的原码为 10011010,即 R = -26。所以,~25 = -26。

按位与:&

按位与 & 操作的作用是将两个操作数的每一位分别进行 逻辑与(&&) 操作。即对应两个位都是 1 ,则得到 1,否则得到 0 。

例:求 25 & -16 = ?

首先,我们分别求得 25 的补码为 00011001,16的补码为 11110000。

计算的结果即为R,则R的补码为 0001000。显然R为正数,则 R 的原码也为 00010000,即 R = 16。所以 25&-16 = 16。

按位或:|

按位或 | 操作的作用是将两个操作数的每一位分别进行 逻辑或( || ) 操作。即对应两个位只要有一位是 1 ,则得到 1,否则得到 0 。

例:求 25 | 35 = ?

首先,很容易我们可以得到25的补码为 00011001,35的补码为  00100011,然后计算:

结果记为R,则 R 的补码为 00111011,显然是正数,则R的原码也为 00111011,即 R = 59。所以 25|35 = 59。

按位异或:^

按位异或 ^ 操作的作用是将两个操作数的每一位分别进行 异或操作,操作逻辑是:如果两个对应位相同,则返回 0;若对应位不同,则返回 1。

例:求 0x39 ^ 0x2a = ?

首先,我们将两个16进制的数转换为二进制的数,并求出它们对应的补码:

然后,进行计算:

记过记为R,则R的补码为 00010011,显然R是个正数。则 R的原码也为 00010011,即 R = 19。所以 0x39 ^ 0x2a = 19。

移位操作符

上面介绍了四种按位操作符,下面,我再介绍三种Java中支持的移位操作符。

移位操作符操作的对象也是数据的二进制位。位移操作只能用来处理整数类型。

如果对 char、byte、或者 short 类型的数值进行移位处理,在移位处理前,它们会被转换为 int 类型(32位),并且得到的也是一个 int 类型的值。并且,只有 移动位数的 低五位 起作用,因为 2^5 = 32,而 int 型值就是32位的。也就是说,移动距离在 0~31 之间。

例如:如果有   25 >> 35。35的补码为:

仅低五位起作用,也就是说,实际上执行的是 25 >> 3 = 3。

如果是处理一个 long 型的值,那最后得到的结果也是 long 型。同理,此时只有移动位数的 低六位 起作用。

在下面的例子中,范例中的数字,都使用1个字节存储。所有数字都是 byte 类型。

左移:<<

左移操作符 << 可以将 操作符左边的操作数 向左移动 操作符右侧指定的位数,并在右侧 补 0 。

例:求 25 << 2 = ?

首先,我们计算出 25 的补码为 00011001,然后进行移位:

上图中,中间那还一部分0,我简化了一下图的画法。

结果记为R,显然R为正数。R补码的低八位为 01100100,因为高位都为0,为了简化计算,我们直接看 低8位。则R低八位的原码也为 01100100,即R = 100。所以 25 << 2 = 100。

带符号右移:>>

带符号右移操作符 >> 可以将 操作符左边的操作数 向右移动 操作符右侧指定的位数。如果符号位为 正,则在高位插入 0;如果符号位为 负,则在高位插入 1 。

例一:求 25 >> 2 = ?

我们很容易得到 25 的补码,低八位为 00011001,进行移位:

结果记为R,显然为正数,R低八位的补码为 00000110。则R低八位的原码也为 00000110,即R = 6。所以 25 >> 2 = 6。

例二:求 -25 >> -30 = ?

很容易求得 -25 的补码。这里需要注意的是,右移的位数为 -30,是个负数。我们求 -30 的补码,低八位如下图:

-25在移位时会被转化为 int 类型,所以移动位数只有低五位才会生效。记生效位数的值为 T,则 T 的原码为 00000010,T = 2。

也就是说, -25 >> -30 实际上等价于 -25 >> 2。然后对其进行移位操作:

结果记为R,显然R为负数。我们对其求补码,得到R对应的原码为

R的低八位为 00000111,符号位为 1,即R = -7。所以 -25 >> 2 = -7。

Ps:

实际上,因为 -25在移位时会自动转换为 int类型。int 类型 32 位,所以,当移动位数为 负数 时,移动结果相当于移动了(移动位数 + 32)。

同理,如果 需要进行移位操作的操作数 是 long 类型的,当移动位数为 负数 时,移动结果相当于移动了(移动位数 + 64)。例如:

289L >> -60 == 289L >> (64-60) == 289L >> 4 == 18。

无符号右移:>>>

无符号右移 >>> 移动方式与 带符号右移 >> 一样,不同的是,无符号右移采用零扩展:无论正负,都在高位插入 0 。这个操作是 C/C++中没有的。

例:求 -25 >>> 2 = ?

直接看移动过程:

结果记为R,显然R为正数。则R的补码和原码相同,都是如图所示,即 R = 1073741817。所以, -25 >>> 2 = 1073741817。

总结

位运算是非常强大的,在项目中合理地运用位运算,可以使代码更有逼格。最主要的原因是,看JDK或一些框架的源码的时候,经常会碰到位运算,不了解位运算,就不能很好滴学习大佬们的代码。总结完位运算,感觉神清气爽啊~

参考文档

1、《C++语言程序设计》第四版,郑莉·著。第二章:C++简单程序设计

2、《Java编程思想》第四版,Bruce Eckel·著。第三章:操作符

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值