1. 程式人生 > >JAVA位移運算子用法

JAVA位移運算子用法

Java定義的位運算(bitwise operators)直接對整數型別的位進行操作,這些整數型別包括long,int,short,char,and byte。表4-2列出了位運算: 

表4.2 位運算子及其結果 

運算子 結果 

~ 按位非(NOT)(一元運算) 

& 按位與(AND) 

| 按位或(OR) 

^ 按位異或(XOR) 

>> 右移 

>>> 右移,左邊空出的位以0填充 


續表 

運算子 結果 

<< 左移 

&= 按位與賦值 

|= 按位或賦值 

^= 按位異或賦值 

>>= 右移賦值 

>>>= 右移賦值,左邊空出的位以0填充 


<<= 左移賦值 

既然位運算子在整數範圍內對位操作,因此理解這樣的操作會對一個值產生什麼效果是重要的。具體地說,知道Java是如何儲存整數值並且如何表示負數的是有用的。因此,在繼續討論之前,讓我們簡短概述一下這兩個話題。

所有的整數型別以二進位制數字位的變化及其寬度來表示。例如,byte型值42的二進位制程式碼是00101010,其中每個位置在此代表2的次方,在最右邊的位以20開始。向左下一個位置將是21,或2,依次向左是22,或4,然後是8,16,32等等,依此類推。因此42在其位置1,3,5的值為1(從右邊以0開始數);這樣42是21+23+25的和,也即是2+8+32 。 

所有的整數型別(除了char型別之外)都是有符號的整數。這意味著他們既能表示正數,又能表示負數。Java使用大家知道的2的補碼(two's complement)這種編碼來表示負數,也就是通過將與其對應的正數的二進位制程式碼取反(即將1變成0,將0變成1),然後對其結果加1。例如,-42就是通過將42的二進位制程式碼的各個位取反,即對00101010取反得到11010101,然後再加1,得到11010110,即-42 。要對一個負數解碼,首先對其所有的位取反,然後加1。例如-42,或11010110取反後為00101001,或41,然後加1,這樣就得到了42。 




如果考慮到零的交叉(zero crossing)問題,你就容易理解Java(以及其他絕大多數語言)這樣用2的補碼的原因。假定byte型別的值零用00000000代表。它的補碼是僅僅將它的每一位取反,即生成11111111,它代表負零。但問題是負零在整數數學中是無效的。為了解決負零的問題,在使用2的補碼代表負數的值時,對其值加1。即負零11111111加1後為100000000。但這樣使1位太靠左而不適合返回到byte型別的值,因此人們規定,-0和0的表示方法一樣,-1的解碼為11111111。儘管我們在這個例子使用了byte型別的值,但同樣的基本的原則也適用於所有Java 的整數型別。 

因為Java使用2的補碼來儲存負數,並且因為Java中的所有整數都是有符號的,這樣應用位運算子可以容易地達到意想不到的結果。例如,不管你如何打算,Java用高位來代表負數。為避免這個討厭的意外,請記住不管高位的順序如何,它決定一個整數的符號。 


位邏輯運算子 

位邏輯運算子有“與”(AND)、“或”(OR)、“異或(XOR)”、“非(NOT)”,分別用“&”、“|”、“^”、“~”表示,4-3表顯示了每個位邏輯運算的結果。在繼續討論之前,請記住位運算子應用於每個運算數內的每個單獨的位。 

表4-3 位邏輯運算子的結果 

A B A | B A & B A ^ B ~A 

0 0 0 0 0 1 

1 0 1 0 1 0 

0 1 1 0 1 1 

1 1 1 1 0 0 

按位非(NOT) 

按位非也叫做補,一元運算子NOT“~”是對其運算數的每一位取反。例如,數字42,它的二進位制程式碼為:00101010

經過按位非運算成為 

11010101 

按位與(AND) 

按位與運算子“&”,如果兩個運算數都是1,則結果為1。其他情況下,結果均為零。 

看下面的例子: 

00101010 42 

&00001111 15 

-------------- 

00001010 10 

按位或(OR) 

按位或運算子“|”,任何一個運算數為1,則結果為1。如下面的例子所示: 

00101010 42 

| 00001111 15 

-------------- 

00101111 47 

按位異或(XOR) 

按位異或運算子“^”,只有在兩個比較的位不同時其結果是 1。否則,結果是零。下面的例子顯示了“^”運算子的效果。這個例子也表明了XOR運算子的一個有用的屬性。注意第二個運算數有數字1的位,42對應二進位制程式碼的對應位是如何被轉換的。第二個運算數有數字0的位,第一個運算數對應位的數字不變。當對某些型別進行位運算時,你將會看到這個屬性的用處。 

00101010 42 

^ 00001111 15 

------------- 

00100101 37 

位邏輯運算子的應用 

下面的例子說明了位邏輯運算子: 

// Demonstrate the bitwise logical operators. 

class BitLogic { 

public static void main(String args[]) { 

String binary[] = { 

"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", 

"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" 

}; 

int a = 3; // 0 + 2 + 1 or 0011 in binary 

int b = 6; // 4 + 2 + 0 or 0110 in binary 

int c = a | b; 

int d = a & b; 

int e = a ^ b; 

int f = (~a & b) | (a & ~b); 

int g = ~a & 0x0f; 

System.out.println(" a = " + binary[a]); 

System.out.println(" b = " + binary[b]); 

System.out.println(" a|b = " + binary[c]); 

System.out.println(" a&b = " + binary[d]); 

System.out.println(" a^b = " + binary[e]); 

System.out.println("~a&b|a&~b = " + binary[f]); 

System.out.println(" ~a = " + binary[g]); 





在本例中,變數a與b對應位的組合代表了二進位制數所有的 4 種組合模式:0-0,0-1,1-0,和1-1。“|”運算子和“&”運算子分別對變數a與b各個對應位的運算得到了變數c和變數d的值。對變數e和f的賦值說明了“^”運算子的功能。字串陣列binary代表了0到15對應的二進位制的值。在本例中,陣列各元素的排列順序顯示了變數對應值的二進位制程式碼。陣列之所以這樣構造是因為變數的值n對應的二進位制程式碼可以被正確的儲存在陣列對應元素binary[n]中。例如變數a的值為3,則它的二進位制程式碼對應地儲存在陣列元素binary[3]中。~a的值與數字0x0f (對應二進位制為0000 1111)進行按位與運算的目的是減小~a的值,保證變數g的結果小於16。因此該程式的執行結果可以用陣列binary對應的元素來表示。該程式的輸出如下: 

a = 0011 

b = 0110 

a|b = 0111 

a&b = 0010 

a^b = 0101 

~a&b|a&~b = 0101 

~a = 1100 



左移運算子 

左移運算子<<使指定值的所有位都左移規定的次數。它的通用格式如下所示: 

value << num 

這裡,num指定要移位值value移動的位數。也就是,左移運算子<<使指定值的所有位都左移num位。每左移一個位,高階位都被移出(並且丟棄),並用0填充右邊。這意味著當左移的運算數是int型別時,每移動1位它的第31位就要被移出並且丟棄;當左移的運算數是long型別時,每移動1位它的第63位就要被移出並且丟棄。 

在對byte和short型別的值進行移位運算時,你必須小心。因為你知道Java在對錶達式求值時,將自動把這些型別擴大為 int型,而且,表示式的值也是int型 。對byte和short型別的值進行移位運算的結果是int型,而且如果左移不超過31位,原來對應各位的值也不會丟棄。但是,如果你對一個負的byte或者short型別的值進行移位運算,它被擴大為int型後,它的符號也被擴充套件。這樣,整數值結果的高位就會被1填充。因此,為了得到正確的結果,你就要捨棄得到結果的高位。這樣做的最簡單辦法是將結果轉換為byte型。下面的程式說明了這一點: 

// Left shifting a byte value. 

class ByteShift { 

public static void main(String args[]) { 

byte a = 64, b; 

int i; 

i = a << 2; 

b = (byte) (a << 2); 

System.out.println("Original value of a: " + a); 

System.out.println("i and b: " + i + " " + b); 





該程式產生的輸出下所示: 

Original value of a: 64 

i and b: 256 0 

因變數a在賦值表示式中,故被擴大為int型,64(0100 0000)被左移兩次生成值256(10000 0000)被賦給變數i。然而,經過左移後,變數b中惟一的1被移出,低位全部成了0,因此b的值也變成了0。 

既然每次左移都可以使原來的運算元翻倍,程式設計師們經常使用這個辦法來進行快速的2的乘法。但是你要小心,如果你將1移進高階位(31或63位),那麼該值將變為負值。下面的程式說明了這一點: 

// Left shifting as a quick way to multiply by 2. 

class MultByTwo { 

public static void main(String args[]) { 

int i; 

int num = 0xFFFFFFE; 

for(i=0; i<4; i++) { 

num = num << 1; 

System.out.println(num); 







該程式的輸出如下所示: 

536870908 

1073741816 

2147483632 

-32 

初值經過仔細選擇,以便在左移 4 位後,它會產生-32。正如你看到的,當1被移進31位時,數字被解釋為負值。 

 右移運算子 

右移運算子>>使指定值的所有位都右移規定的次數。它的通用格式如下所示: 

value >> num 

這裡,num指定要移位值value移動的位數。也就是,右移運算子>>使指定值的所有位都右移num位。 

下面的程式片段將值32右移2次,將結果8賦給變數a: 

int a = 32; 

a = a >> 2; // a now contains 8 

當值中的某些位被“移出”時,這些位的值將丟棄。例如,下面的程式片段將35右移2次,它的2個低位被移出丟棄,也將結果8賦給變數a: 

int a = 35; 

a = a >> 2; // a still contains 8 

用二進位制表示該過程可以更清楚地看到程式的執行過程: 

00100011 35 

>> 2 

00001000 8 

將值每右移一次,就相當於將該值除以2並且捨棄了餘數。你可以利用這個特點將一個整數進行快速的2的除法。當然,你一定要確保你不會將該數原有的任何一位移出。 

右移時,被移走的最高位(最左邊的位)由原來最高位的數字補充。例如,如果要移走的值為負數,每一次右移都在左邊補1,如果要移走的值為正數,每一次右移都在左邊補0,這叫做符號位擴充套件(保留符號位)(sign extension),在進行右移操作時用來保持負數的符號。例如,–8 >> 1 是–4,用二進位制表示如下: 

11111000 –8 

>>1 

11111100 –4 

一個要注意的有趣問題是,由於符號位擴充套件(保留符號位)每次都會在高位補1,因此-1右移的結果總是–1。有時你不希望在右移時保留符號。例如,下面的例子將一個byte型的值轉換為用十六進位制表示。注意右移後的值與0x0f進行按位與運算,這樣可以捨棄任何的符號位擴充套件,以便得到的值可以作為定義陣列的下標,從而得到對應陣列元素代表的十六進位制字元。 

// Masking sign extension. 

class HexByte { 

static public void main(String args[]) { 

char hex[] = { 

'0', '1', '2', '3', '4', '5', '6', '7', 

'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'' 

}; 

byte b = (byte) 0xf1; 

System.out.println("b = 0x" + hex[(b >> 4) & 0x0f] + hex[b & 0x0f]); 





該程式的輸出如下: 

b = 0xf1 



 無符號右移 

正如上面剛剛看到的,每一次右移,>>運算子總是自動地用它的先前最高位的內容補它的最高位。這樣做保留了原值的符號。但有時這並不是我們想要的。例如,如果你進行移位操作的運算數不是數字值,你就不希望進行符號位擴充套件(保留符號位)。當你處理畫素值或圖形時,這種情況是相當普遍的。在這種情況下,不管運算數的初值是什麼,你希望移位後總是在高位(最左邊)補0。這就是人們所說的無符號移動(unsigned shift)。這時你可以使用Java的無符號右移運算子>>>,它總是在左邊補0。下面的程式段說明了無符號右移運算子>>>。在本例中,變數a被賦值為-1,用二進位制表示就是32位全是1。這個值然後被無符號右移24位,當然它忽略了符號位擴充套件,在它的左邊總是補0。這樣得到的值255被賦給變數a。 

int a = -1; 

a = a >>> 24; 

下面用二進位制形式進一步說明該操作: 

11111111 11111111 11111111 11111111 int型- 1的二進位制程式碼 

>>> 24 無符號右移24位 

00000000 00000000 00000000 11111111 int型255的二進位制程式碼由於無符號右移運算子>>>只是對32位和64位的值有意義,所以它並不像你想象的那樣有用。因為你要記住,在表示式中過小的值總是被自動擴大為int型。這意味著符號位擴充套件和移動總是發生在32位而不是8位或16位。這樣,對第7位以0開始的byte型的值進行無符號移動是不可能的,因為在實際移動運算時,是對擴大後的32位值進行操作。下面的例子說明了這一點: 

// Unsigned shifting a byte value. 

class ByteUShift { 

static public void main(String args[]) { 

char hex[] = { 

'0', '1', '2', '3', '4', '5', '6', '7', 

'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 

}; 

byte b = (byte) 0xf1; 

byte c = (byte) (b >> 4); 

byte d = (byte) (b >>> 4); 

byte e = (byte) ((b & 0xff) >> 4); 

System.out.println(" b = 0x" 

+ hex[(b >> 4) & 0x0f] + hex[b & 0x0f]); 

System.out.println(" b >> 4 = 0x" 

+ hex[(c >> 4) & 0x0f] + hex[c & 0x0f]); 

System.out.println(" b >>> 4 = 0x" 

+ hex[(d >> 4) & 0x0f] + hex[d & 0x0f]); 

System.out.println("( b & 0xff) >> 4 = 0x" 

+ hex[(e >> 4) & 0x0f] + hex[e & 0x0f]); 





該程式的輸出顯示了無符號右移運算子>>>對byte型值處理時,實際上不是對byte型值直接操作,而是將其擴大到int型後再處理。在本例中變數b被賦為任意的負byte型值。對變數b右移4位後轉換為byte型,將得到的值賦給變數c,因為有符號位擴充套件,所以該值為0xff。對變數b進行無符號右移4位操作後轉換為byte型,將得到的值賦給變數d,你可能期望該值是0x0f,但實際上它是0xff,因為在移動之前變數b就被擴充套件為int型,已經有符號擴充套件位。最後一個表示式將變數b的值通過按位與運算將其變為8位,然後右移4位,然後將得到的值賦給變數e,這次得到了預想的結果0x0f。由於對變數d(它的值已經是0xff)進行按位與運算後的符號位的狀態已經明瞭,所以注意,對變數d再沒有進行無符號右移運算。 

B = 0xf1 

b >> 4 = 0xff 

b >>> 4 = 0xff 

(b & 0xff) >> 4 = 0x0f 



位運算子賦值 

所有的二進位制位運算子都有一種將賦值與位運算組合在一起的簡寫形式。例如,下面兩個語句都是將變數a右移4位後賦給a: 

a = a >> 4; 

a >>= 4; 

同樣,下面兩個語句都是將表示式a OR b運算後的結果賦給a: 

a = a | b; 

a |= b; 

下面的程式定義了幾個int型變數,然後運用位賦值簡寫的形式將運算後的值賦給相應的變數: 

class OpBitEquals { 

public static void main(String args[]) { 

int a = 1; 

int b = 2; 

int c = 3; 

a |= 4; 

b >>= 1; 

c <<= 1; 

a ^= c; 

System.out.println("a = " + a); 

System.out.println("b = " + b); 

System.out.println("c = " + c); 





該程式的輸出如下所示: 

a = 3 

b = 1 

c = 6