一 基本概念
全部的整數類型以二進制數字位的變化及其寬度來表示。例如,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 的整數類型。 ui 由於Java 使用2的補碼來存儲負數,而且由於Java 中的全部整數都是有符號的,這樣應用位運算符能夠容易地達到意想不到的結果。例如,無論你如何打算,Java 用高位來表明負數。爲避免這個討厭的意外,請記住無論高位的順序如何,它決定一個整數的符號。 按位非(NOT) spa 按位非也叫作補,一元運算符NOT「~」是對其運算數的每一位取反。例如,數字42,它的二進制代碼爲: ip 00101010 字符串 通過按位非運算成爲 數學 11010101 it 按位與(AND) io 按位與運算符「&」,若是兩個運算數都是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. String binary[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" }; System.out.println(" a = " + binary[a]); } 在本例中,變量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 在對byte 和short類型的值進行移位運算時,你必須當心。由於你知道Java 在對錶達式求值時,將自動把這些類型擴大爲 int 型,並且,表達式的值也是int 型。對byte 和short類型的值進行移位運算的結果是int 型,並且若是左移不超過31位,原來對應各位的值也不會丟棄。可是,若是你對一個負的byte 或者short類型的值進行移位運算,它被擴大爲int 型後,它的符號也被擴展。這樣,整數值結果的高位就會被1填充。所以,爲了獲得正確的結果,你就要捨棄獲得結果的高位。這樣作的最簡單辦法是將結果轉換爲byte 型。下面的程序說明了這一點: // Left shifting a byte value. public static void main(String args[]) { i = a << 2; System.out.println("Original value of a: " + a); 該程序產生的輸出下所示: Original value of a: 64 因變量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. public static void main(String args[]) { for(i=0; i<4; i++) { } 在對byte 和short類型的值進行移位運算時,你必須當心。由於你知道Java 在對錶達式求值時,將自動把這些類型擴大爲 int 型,並且,表達式的值也是int 型。對byte 和short類型的值進行移位運算的結果是int 型,並且若是左移不超過31位,原來對應各位的值也不會丟棄。可是,若是你對一個負的byte 或者short類型的值進行移位運算,它被擴大爲int 型後,它的符號也被擴展。這樣,整數值結果的高位就會被1填充。所以,爲了獲得正確的結果,你就要捨棄獲得結果的高位。這樣作的最簡單辦法是將結果轉換爲byte 型。下面的程序說明了這一點: // Left shifting a byte value. public static void main(String args[]) { i = a << 2; System.out.println("Original value of a: " + a); 該程序產生的輸出下所示: Original value of a: 64 因變量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. public static void main(String args[]) { for(i=0; i<4; i++) { } 該程序的輸出以下所示: 536870908 初值通過仔細選擇,以便在左移 4 位後,它會產生-32。正如你看到的,當1被移進31 位時,數字被解釋爲負值。 value >> num 這裏,num 指定要移位值value 移動的位數。也就是,右移運算符>>使指定值的全部位都右移num位。下面的程序片斷將值32右移2次,將結果8賦給變量a: int a = 32; 當值中的某些位被「移出」時,這些位的值將丟棄。例如,下面的程序片斷將35右移2 次,它的2個低位被移出丟棄,也將結果8賦給變量a: int a = 35; 用二進制表示該過程能夠更清楚地看到程序的運行過程: 00100011 35 將值每右移一次,就至關於將該值除以2而且捨棄了餘數。你能夠利用這個特色將一個整數進行快速的2的除法。固然,你必定要確保你不會將該數原有的任何一位移出。 右移時,被移走的最高位(最左邊的位)由原來最高位的數字補充。例如,若是要移走的值爲負數,每一次右移都在左邊補1,若是要移走的值爲正數,每一次右移都在左邊補0,這叫作符號位擴展(保留符號位)(sign extension ),在進行右移操做時用來保持負數的符號。例如,–8 >> 1 是–4,用二進制表示以下: 11111000 –8 >>1 11111100 –4 一個要注意的有趣問題是,因爲符號位擴展(保留符號位)每次都會在高位補1,所以-1右移的結果老是–1。有時你不但願在右移時保留符號。例如,下面的例子將一個byte 型的值轉換爲用十六 // Masking sign extension. char hex[] = { }; System.out.println("b = 0x" + hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);}} 該程序的輸出以下: b = 0xf1 下面的程序段說明了無符號右移運算符>>> 。在本例中,變量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. a |= 4; } 該程序的輸出以下所示: a = 3 b = 1 c = 6 |