徹底弄懂Java的移位操作符

NO IMAGE

前言

對於移位操作符,很多人既感到熟悉,又感到陌生。熟悉是因為移位操作符是最基本的操作符之一,幾乎每種編程語言都包含這一操作符;陌生是因為除非是追求極致性能等罕見場景,否則也很難用得上它。打開JDK源碼,你會發現移位操作符的身影極為常見,弄清楚它的用法,對閱讀源碼很有幫助。

移位操作是把數據看作是二進制數,然後將其向左或向右移動若干位的運算。在Java編程語言中,移位操作符包含三種,分別是 <<(左移)、 >>(帶符號右移)和 >>>(無符號右移),這三種操作符都只能作用於longintshortbytechar這四種基本的整型類型上。

左移操作符 <<

左移操作符 << 是將數據轉換成二進制數後,向左移若干位,高位丟棄,低位補零。看如下例子:

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的int佔32位,因此對i = -1轉換成二進制數,然後左移10位,其結果是左邊高10位丟棄,右邊低10位補0,再轉換為十進制,得到i = -1024的結果。

徹底弄懂Java的移位操作符
int -1 << 10 = -1024

因此,上述例子的輸出結果為:

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

帶符號右移操作符 >>

眾所周知,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的結果。

徹底弄懂Java的移位操作符
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的結果。

徹底弄懂Java的移位操作符
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

徹底弄懂Java的移位操作符
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

因此,第一個例子中實際的運算過程應該是這樣:

徹底弄懂Java的移位操作符
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

徹底弄懂Java的移位操作符
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

徹底弄懂Java的移位操作符
int -1 >>> 32 = -1

同理,對於i4 >>> 33等同於i4 >>> 1,其結果為2147483647

徹底弄懂Java的移位操作符
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代碼裡使用 <<>> 轉換成的指令碼運行起來會更高效些。

徹底弄懂Java的移位操作符

相關文章

Promises/A+實現,一條規範對應一段代碼

JS類型

js循環語句中的async與await

使用ReactHook新特性遇到的一些問題