preface
Shift operators are both familiar and unfamiliar to many people. Familiar because the shift operator is one of the most basic operators and is included in almost every programming language; Strange because it is hard to use except for rare situations such as extreme performance. If you open the JDK source code, you’ll find that the shift operator is very common, and it’s helpful to know how to use it.
A shift operation is an operation that treats data as a binary number and moves it several bits to the left or right. In the Java programming language, there are three types of shift operators: << (left shift), >> (signed right shift), and >>> (unsigned right shift), all of which operate only on the four basic integer types long, int, short, byte, and char.
Left-shift operator <<
The left-shift operator << is used to convert data into binary numbers and move the data several bits to the left, discarding the high bits and filling the low bits. Here’s an 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));
}
Copy the code
Java int is 32 bits, so I = -1 is converted to binary, then moved 10 bits left, resulting in the left 10 bits discarded, the right 10 bits added 0, and converted to decimal, I = -1024.
Thus, the output of the above 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
Copy the code
Signed right shift operator >>
As we all know, when integers represent negative numbers in Java, the highest bit is the sign bit, the positive number is 0, and the negative number is 1. >> is a signed right-shift operator. After converting data to binary numbers, it moves several bits to the right, filling the high sign bit and discarding the low one. When the positive number is moved to the right, it is embodied in the high position to complement 0. Negative numbers add 1. Here’s an example:
public static void main(String[] args) {
// Shift positive numbers to the right
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));
// Shift negative numbers to the right
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));
}
Copy the code
In the example, i1 = 4992 is converted to binary and moved 10 bits to the right. The result is that the higher 10 bits on the left are added to 0, the lower 10 bits on the right are discarded, and then converted to decimal, resulting in i1 = 4. Similarly, i2 = -4992, move 10 bits to the right, add 1 for the 10 bits higher on the left, discard the 10 bits lower on the right, and get i2 = -5.
Thus, the output of the above example is:
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
Copy the code
Unsigned right shift operator >>>
The unsigned right-shift operator >>> is similar to the >> operator in that it converts data to a binary number and moves it several bits to the right, except that the result is zeroing in on the high and discarding the low, whether negative or not. Here’s an example:
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));
}
Copy the code
The same operation is performed on i3 = -4992. After the binary number is converted, the right 10 bits are shifted to the right. The result is that the higher 10 bits on the left are added to 0, the lower 10 bits on the right are discarded, and then converted to decimal, resulting in i3 = 4194299.
Thus, the output of the above example is:
Before >>> , i3's value is -4992
i3's binary string is 11111111111111111110110010000000
After >>> , i3's value is 4194299
i3's binary string is 1111111111111111111011
Copy the code
Do you really understand?
Shift operations on short, byte, char
Here’s another example:
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));
}
Copy the code
Java byte takes up 8 bits. According to the principle described above, after converting b = -1 to binary, move 6 bits to the right, add 0 with 6 bits higher on the left, discard the lower on the right, and the result should be B = 3.
Is that true? Let’s look at the result of the example run:
Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is -1
b's binary string is 11111111111111111111111111111111
Copy the code
The results are not what we expected!
Java converts byte, short, and char shifts to int before processing them. In particular, when you use <<=, >>=, and >>>=, you get the result of a low truncation of a shifted int! Verify with a change to the example:
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));
}
Copy the code
In this example, instead of reassigning b with >>>=, b >>> 6 is printed directly (note that b >>> 6 is an int), and the output is as follows:
Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is 67108863
b's binary string is 11111111111111111111111111
Copy the code
So the actual operation in the first example would look like this:
The same is true for short and char shifts, which you can test for yourself.
What happens if the number of digits shifted exceeds the number of digits occupied by the value?
In all the cases so far, the number of digits shifted is within the number of digits occupied by the value. For example, the number of digits shifted for int has not exceeded 32. So what happens if you shift an int by more than 32 bits? Take a look at the following example:
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));
}
Copy the code
For i4 >>> 31 we can easily get a result of 1 based on the principle described earlier.
So,i4 >>> 32
The result will be0
?
NO! Java has special treatment for the right operand RHS of the shift operator. For int, only the lower 5 bits are taken, which is the result of RHS % 32. For long, only the lower 6 bits are taken, which is the result of RHS % 64. So, for i4 >>> 32, which is actually I4 >>> (32%32), which is i4 >>> 0, the result is still -1.
Similarly, for i4 >>> 33 is equivalent to I4 >>> 1, the result is 2147483647.
Therefore, the output of the above example is as follows:
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
Copy the code
The same is true for long types, which you can test for yourself.
conclusion
The shift operator is one of the most basic operators in Java, but if you don’t know the details thoroughly, you can make mistakes. The shift operator actually supports only int and long, and the compiler will convert short, byte, and char to int before performing the shift. The most common use of the shift operator in JDK source code is to use it as the multiply * or divide/operator: to move an integer one bit to the left equals to multiply by 2; If we move to the right one, that’s the same thing as dividing by 2. The reason is that it is much more efficient to use << and >> converted instructions in Java code than to use * and /.