Java completely understand the shift operators

Foreword

For shift operators , many people feel both familiar and unfamiliar. Familiar because the shift operators is one of the most basic operator, almost every programming language contains the operator; strange because unless the pursuit of extreme performance and other rare scene, otherwise it is difficult to need them. Open JDK source code, you will find that the shift operator figure is extremely common, figure out its use, helpful to read the source code.

Data shift operation is regarded as a binary number, then a number of bits left or right operation. In the Java programming language, comprises three shift operators, respectively <<(left), >>(right shift with sign) and >>>(unsigned shift right), the three operators can only act on the long, int, short, byte, charwhich four kinds of basic integer types.

The left shift operator <<

The left shift operator <<after converting the data into a binary number, to the left a number of bits, discarding the upper, lower zero-padding . Look at the following example:

public static void main(String[] args) {
    int i = -1;
    System.out.println("Before << , i's value is " + i);
    System.out.println("i's binary string is " + Integer.toBinaryString(i));
    i <<= 10;
    System.out.println("After << , i's value is " + i);
    System.out.println("i's binary string is " + Integer.toBinaryString(i));
}
复制代码

Java's intaccount 32, therefore i = -1converted into binary numbers, and left 10, which result is a high 10 to discard the left, to the right of the lower 10 bits up 0, and then converted to decimal to obtain i = -1024results.

int -1 << 10 = -1024
int -1 << 10 = -1024

Accordingly, the output result of the example is:

Before << , i's value is -1
i's binary string is 11111111111111111111111111111111
After << , i's value is -1024
i's binary string is 11111111111111111111110000000000
复制代码

Signed right shift operator >>

众所周知,Java中整型表示负数时,最高位为符号位,正数为0,负数为1>> 是带符号的右移操作符,将数据转换成二进制数后,向右移若干位,高位补符号位,低位丢弃。对于正数作右移操作时,具体体现为高位补0;负数则补1。看如下例子:

public static void main(String[] args) {
    // 对正数进行右移操作
    int i1 = 4992;
    System.out.println("Before >> , i1's value is " + i1);
    System.out.println("i1's binary string is " + Integer.toBinaryString(i1));
    i1 >>= 10;
    System.out.println("After >> , i1's value is " + i1);
    System.out.println("i1's binary string is " + Integer.toBinaryString(i1));
    // 对负数进行右移操作
    int i2 = -4992;
    System.out.println("Before >> , i2's value is " + i2);
    System.out.println("i2's binary string is " + Integer.toBinaryString(i2));
    i2 >>= 10;
    System.out.println("After >> , i2's value is " + i2);
    System.out.println("i2's binary string is " + Integer.toBinaryString(i2));
}
复制代码

例子中,i1 = 4992转换成二进制数,右移10位,其结果是左边高10位补0,右边低10位丢弃,再转换为十进制,得到i1 = 4的结果。同理,i2 = -4992,右移10位,左边高10位补1,右边低10位丢弃,得到i2 = -5的结果。

int 4992 >> 10 = 4 和 int -4992 >> 10 = -5
int 4992 >> 10 = 4 和 int -4992 >> 10 = -5

因此,上述例子的输出结果为:

Before >> , i1's value is 4992
i1's binary string is 1001110000000
After >> , i1's value is 4
i1's binary string is 100
Before >> , i2's value is -4992
i2's binary string is 11111111111111111110110010000000
After >> , i2's value is -5
i2's binary string is 11111111111111111111111111111011
复制代码

无符号右移操作符 >>>

无符号右移操作符 >>>>>类似,都是将数据转换为二进制数后右移若干位,不同之处在于,不论负数与否,结果都是高位补零,低位丢弃。看如下例子:

public static void main(String[] args) {
    int i3 = -4992;
    System.out.println("Before >>> , i3's value is " + i3);
    System.out.println("i3's binary string is " + Integer.toBinaryString(i3));
    i3 >>>= 10;
    System.out.println("After >>> , i3's value is " + i3);
    System.out.println("i3's binary string is " + Integer.toBinaryString(i3));
}
复制代码

同样对i3 = -4992进行操作,转换成二进制数后,右移10位,其结果为左边高10位补0,右边低10位丢弃,再转换成十进制,得到i3 = 4194299的结果。

int -4992 >>> 10 = 4194299
int -4992 >>> 10 = 4194299

因此,上述例子的输出结果为:

Before >>> , i3's value is -4992
i3's binary string is 11111111111111111110110010000000
After >>> , i3's value is 4194299
i3's binary string is 1111111111111111111011
复制代码

真的懂了吗?

对 short、byte、char 的移位操作

再看如下例子:

public static void main(String[] args) {
    byte b = -1;
    System.out.println("Before >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
    b >>>= 6;
    System.out.println("After >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
}
复制代码

Java的byte占8位,按照前面讲述的原理,对b = -1转换为二进制数后,右移6位,左边高6位补0,右边低位丢弃,其结果应该是b = 3

int -1 >>> 6 = 3 ?
int -1 >>> 6 = 3 ?

真的这样吗?我们看一下例子运行的结果:

Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is -1
b's binary string is 11111111111111111111111111111111
复制代码

运行结果与我们预期的结果不对!

原来,Java在处理byteshortchar的移位操作前,会先将其转型成int类型,然后在进行操作!特别地,当对这三者使用<<=>>=>>>=时,其实是得到对移位后的int进行低位截断后的结果!对例子改动一下进行验证:

public static void main(String[] args) {
    byte b = -1;
    System.out.println("Before >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
    System.out.println("After >> , b's value is " + (b >>> 6));
    System.out.println("b's binary string is " + Integer.toBinaryString(b >>> 6));
}
复制代码

在该例子中,没有使用 >>>=b 进行再赋值,而是直接将 b >>> 6 进行输出(需要注意的是,b >>> 6 的结果为 int 类型),其输出如下:

Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is 67108863
b's binary string is 11111111111111111111111111
复制代码

因此,第一个例子中实际的运算过程应该是这样:

byte -1 >>> 6 = -1
byte -1 >>> 6 = -1

对于shortchar的移位操作原理也一样,读者可以自行进行实验验证。

如果移位的位数超过数值所占有的位数会怎样?

到目前为止的所有的例子中,移位的位数都在数值所占有的位数之内,比如对int类型的移位都没有超过32。那么如果对int类型移位超过32位会怎样?且看如下例子:

public static void main(String[] args) {
    int i4 = -1;
    System.out.println("Before >>> , i4's value is " + i4);
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4));
    System.out.println("After >>> 31 , i4's value is " + (i4 >>> 31));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 31));
    System.out.println("After >>> 32 , i4's value is " + (i4 >>> 32));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 32));
    System.out.println("After >>> 33 , i4's value is " + (i4 >>> 33));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 33));
}
复制代码

根据前面讲述的原理,对于i4 >>> 31我们很容易得出结果为1

int -1 >>> 31 = 1
int -1 >>> 31 = 1

那么,i4 >>> 32的结果会是0吗?

NO!Java对移位操作符的右操作数rhs有特别的处理,对于int类型,只取其低5位,也就是取rhs % 32的结果;对于long类型,只取其低6位,也即是取rhs % 64的结果。因此,对于i4 >>> 32,实际上是i4 >>> (32 % 32),也即i4 >>> 0,结果仍然是-1

int -1 >>> 32 = -1
int -1 >>> 32 = -1

同理,对于i4 >>> 33等同于i4 >>> 1,其结果为2147483647

int -1 >>> 33 = 2147483647
int -1 >>> 33 = 2147483647

因此,上述例子的输出结果如下:

Before >>> , i4's value is -1
i4's binary string is 11111111111111111111111111111111
After >>> 31 , i4's value is 1
i4's binary string is 1
After >>> 32 , i4's value is -1
i4's binary string is 11111111111111111111111111111111
After >>> 33 , i4's value is 2147483647
i4's binary string is 1111111111111111111111111111111
复制代码

对于long类型也是同样的道理,读者可以自行进行实验验证。

总结

移位操作符虽说是Java中最基本的操作符之一,但是若不彻底弄清楚其中细节,稍有不慎,便容易犯错。移位操作符实际上支持的类型只有intlong,编译器在对shortbytechar类型进行移位前,都会将其转换为int类型再操作。移位操作符在JDK源码中,最常见的用法便是将其当成是乘 * 或者除 / 操作符使用 :对一个整型左移一位,相当于乘以2;右移一位,相当于除以2。其中原因就是,相比于使用 */ ,在Java代码里使用 <<>> 转换成的指令码运行起来会更高效些。

Guess you like

Origin juejin.im/post/5dff47416fb9a0162f62271c