位运算

概述

程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对数字在内存中的bit位,即二进制位进行操作。在java中位运算分为按位操作符和移位操作符两种。

按位操作符

&(按位与,and)

只有在相同位都是1的情况下输出为1,否则输出为0。例如数字5(注意本章所有的例子中的操作数类型都是byte类型的)的二进制为00000101,数字6的二进制为00000110,举例5 & 6的计算过程:

1
2
3
4
5
00000101
--------
00000110
↓↓↓↓↓↓↓↓
00000100

5 & 6运算的二进制结果为00000100,对应的十进制值为4

|(按位或,or)

相同位只要有一个是1,则输出1,只有相同位都是0的情况下输出才为0。举例5 | 6的计算过程:

1
2
3
4
5
00000101
--------
00000110
↓↓↓↓↓↓↓↓
00000111

5 | 6运算的二进制结果为00000111,对应的十进制值为7

^(异或运算,xor)

相同位上的值如果相等则输出0,不相等则输出1。举例5 ^ 6的计算过程:

1
2
3
4
5
00000101
--------
00000110
↓↓↓↓↓↓↓↓
00000011

5 ^ 6运算的二进制结果为00000011,对应的十进制值为3。注意两个数在两次异或后的结果不变,也就是说(a ^ b) ^ b = a。

~(非运算,not)

非运算也称作取反运算,非运算属于一元操作符,只对一个操作数进行操作。它将每位上的值取反,也就是说如果输入为1则输出为0,输入为0则输出为1。举例~5的计算过程:

1
2
3
00000101
↓↓↓↓↓↓↓↓
11111010

~5运算的二进制结果为11111010,对应的十进制值为-6

移位操作符

移位操作符操作的运算对象也是二进制的bit,分为左移操作符和右移位操作符。有以下几点需要注意

  1. 移位操作符只能用来处理整数类型,不能处理double和float类型的数值
  2. 如果处理的数据是char、byte、short中的一种,在进行移位处理前,它们会被转换为int类型的数值,并且最终移位后的结果也是int类型,如果处理的数据是long类型的,那么结果也是long类型的
  3. 为了保证移动位数的有效性,例如int类型为32位,那么移动位数不能超过32位,即在移位的时候会将位数转换成二进制后的后五位所表示的数值作为最终的移位数,例如8 << 100很明显是一个溢出的操作,100对应二进制的最右边5位是00100,表示的值为4,所以最终8 << 100会被转换为8 << 4=128。当然啦对于long类型的移位操作的规则和int类型一样,只不过当移动位数超过64位时,会将位数转换成二进制后的后六位所表示的数值作为最终的移位数。

<<(左移位操作符,lsh)

左移位运算能按照操作符右侧指定的位数将操作符左侧的操作数向左移动(在低位补0)。

数学意义

在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。

举例5 << 2的计算过程(注意5是byte类型的移位前会被转换为int类型)

1
2
3
00000000 00000000 00000000 00000101
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
00000000 00000000 00000000 00010100

5 << 2运算的二进制结果为00000000 00000000 00000000 00010100,对应的十进制值为20

右移位操作符(rsh)

java中有一共有两种右移位操作,分别是>>(有符号的右移位操作)和>>>(无符号的右移位操作)

>>(有符号的右移位操作)

有符号的右移位操作按照操作符右侧指定的位数将操作符左侧的操数向右侧移动。有符号的右移位操作使用了符号扩展,也就是说如果操作数为正的话则在高位补0,若操作数为负的话则在高位补1
数学意义

每右移一位相当于除2,右移n位相当于除以2的n次方,如果无法整除的话,结果向下取整(这里的下指的是相对于负无穷的方向)

举例5 >> 2的计算过程(注意5是byte类型的,移位前会被转换为int类型)

1
2
3
00000000 00000000 00000000 00000101
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
00000000 00000000 00000000 00000001

5 >> 2运算的二进制结果为00000000 00000000 00000000 00000001,对应的十进制值为1。

举例-5 >> 2的计算过程(注意5是byte类型的,移位前会被转换为int类型)

1
2
3
‭11111111 11111111 11111111 11111011‬
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
‭11111111 11111111 11111111 11111110‬

-5 >> 2运算的二进制结果为‭11111111 11111111 11111111 11111110,‬对应的十进制值为-2。

>>>(无符号的右移位操作)

无符号的右移位操作按照操作符右侧指定的位数将操作符左侧的操数向右侧移动。它和有符号的右移位操作不同的地方在于它使用零扩展,无论操作数是否正负,都在高位补0。

数学意义

  • 正数

    每右移一位相当于除2,右移n位相当于除以2的n次方,如果无法整除的话,结果向下取整(这里的下指的是相对于负无穷的方向)

  • 负数

    负数经过无符号右移操作后的结果为以下公式计算的结果

    对应的java代码的实现如下

    1
    number >>> step == (int) (Math.pow(2, 32 - step) - Math.ceil(Math.abs(number) / Math.pow(2, step)))

举例5 >>> 2的计算过程(注意5是byte类型的,移位前会被转换为int类型)

1
2
3
00000000 00000000 00000000 00000101
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
00000000 00000000 00000000 00000001

5 >>> 2运算的二进制结果为00000000 00000000 00000000 00000001,对应的值为1。举例-5 >>> 2的计算过程(注意5是byte类型的,移位前会被转换为int类型)

1
2
3
‭11111111 11111111 11111111 11111011‬
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
00111111 11111111 11111111 11111110‬

-5 >>> 2运算的二进制结果为00111111 11111111 11111111 11111110‬,对应的十进制值为1073741822。