[大資料量]java移位運算與位運算與資料型別的儲存

大搜車-自娛發表於2012-08-01
[i]站在巨人的肩膀上,參考其他部落格文章,彙總於此:[/i]

1G=1024M=1024K=1024BYTE=8BIT

[b]Java資料型別的儲存:(舉例說明)[/b]
問:int 型的在記憶體中怎麼儲存,假設 int a=21;那麼a是怎麼被存到計算機裡的。怎樣把 a 變成二進位制數。
怎麼樣把 a 存到 byte[]陣列中?

答:int佔4個位元組,byte是1個位元組,每個位元組8位。
所以2進位制的話,int最多可以表示正負一共2的32次方個數,byte則是2的8次方。

e.g.

int=21;在計算機中表示為00000000,00000000,00000000,00010101
byte=21;就是00010101
byte轉換int直接賦值,int轉byte強制型別轉換,因為涉及精度。




[b]移位運算子[/b]就是在二進位制的基礎上對數字進行平移。按照平移的方向和填充數字的規則分為三種:<<(左移)、>>(帶符號右移)和>>>(無符號右移)。
  在移位運算時,byte、short和char型別移位後的結果會變成int型別,對於byte、short、char和int進行移位時,規定實際移動的次數是移動次數和32的餘數,也就是移位33次和移位1次得到的結果相同。移動long型的數值時,規定實際移動的次數是移動次數和64的餘數,也就是移動66次和移動2次得到的結果相同。

示例:
System.out.println(11<<2);
System.out.println(11<<34);

System.out.println(1111111111111111111L>>>2);
System.out.println(1111111111111111111L>>>66);

結果:
 44
44
277777777777777777
277777777777777777

  三種移位運算子的移動規則和使用如下所示:
  [b]<<運算規則[/b]:按二進位制形式把所有的數字向左移動對應的位數,高位移出(捨棄),低位的空位補零。
  [b]語法格式:[/b]
  需要移位的數字 << 移位的次數
  例如: 3 << 2,則是將數字3左移2位
  [b]計算過程:[/b]
  3 << 2
  首先把3轉換為二進位制數字0000 0000 0000 0000 0000 0000 0000 0011,然後把該數字高位(左側)的兩個零移出,其他的數字都朝左平移2位,最後在低位(右側)的兩個空位補零。則得到的最終結果是0000 0000 0000 0000 0000 0000 0000 1100,則轉換為十進位制是12.數學意義:
  在數字沒有溢位的前提下,對於正數和負數,左移一位都相當於乘以2的1次方,左移n位就相當於乘以2的n次方。
  [b]>>運算規則[/b]:按二進位制形式把所有的數字向右移動對應巍峨位數,低位移出(捨棄),高位的空位補符號位,即正數補零,負數補1.
  [b]語法格式:[/b]
  需要移位的數字 >> 移位的次數
  例如11 >> 2,則是將數字11右移2位
  計算過程:11的二進位制形式為:0000 0000 0000 0000 0000 0000 0000 1011,然後把低位的最後兩個數字移出,因為該數字是正數,所以在高位補零。則得到的最終結果是0000 0000 0000 0000 0000 0000 0000 0010.轉換為十進位制是[b]2[/b].數學意義:右移一位相當於除2,右移n位相當於除以2的n次方。

[b]>>>運算規則[/b]:按二進位制形式把所有的數字向右移動對應巍峨位數,低位移出(捨棄),高位的空位補零。對於正數來說和帶符號右移相同,對於負數來說不同。
  其他結構和>>相似。

舉例:
byte a = 27;// 轉換成int為 00000000000000000000000000011011
byte b = -1;轉換成int為 11111111111111111111111111111111

g = a >>> 1;// 無符號右移1位,左側缺的位以0補齊, "00000000000000000000000000001101" = 13
f = b>>> 1; // 無符號右移1位,左側缺的位以0補齊, “01111111111111111111111111111111”= 2147483647

故此時列印出來,g=13,f=2147483647。
  小結
  二進位制運算子,包括位運算子和移位運算子,使程式設計師可以在二進位制基礎上運算元字,可以更有效的進行運算,並且可以以二進位制的形式儲存和轉換資料,是實現網路協議解析以及加密等演算法的基礎。
[b]示例程式碼[/b]

public class URShift {
public static void main(String[] args) {
//int i = -1;
//i >>>= 10;
//System.out.println(i);
mTest();
}

public static void mTest() {
// 左移
int i = 12; // 二進位制為:0000000000000000000000000001100
i <<= 2; // i左移2位,把高位的兩位數字(左側開始)拋棄,低位的空位補0,二進位制碼就為0000000000000000000000000110000
System.out.println(i); // 二進位制110000值為48;
System.out.println("<br>");
// 右移
i >>= 2; // i右移2為,把低位的兩個數字(右側開始)拋棄,高位整數補0,負數補1,二進位制碼就為0000000000000000000000000001100
System.out.println(i); // 二進位制碼為1100值為12
System.out.println("<br>");
// 右移example
int j = 11;// 二進位制碼為00000000000000000000000000001011
j >>= 2; // 右移兩位,拋棄最後兩位,整數補0,二進位制碼為:00000000000000000000000000000010
System.out.println(j); // 二進位制碼為10值為2
System.out.println("<br>");
byte k = -2; // 轉為int,二進位制碼為:0000000000000000000000000000010
k >>= 2; // 右移2位,拋棄最後2位,負數補1,二進位制嗎為:11000000000000000000000000000
System.out.println(j); // 二進位制嗎為11值為2
}
}



ThinkingInJava原話:
在Thinking in Java第三章中的一段話:
  移位運算子面向的運算物件也是
  二進位制的“位”。 可單獨用它們處理整數型別(主型別的一種)。左移位運算子(<<)能將運算子左邊的運算物件向左移動運算子右側指定的位數(在低位補0)。 “有符號”右移位運算子(>>)則將運算子左邊的運算物件向右移動運算子右側指定的位數。“有符號”右移位運算子使用了“符號擴充套件”:若值為正,則在高位插入0;若值為負,則在高位插入1。Java也新增了一種“無符號”右移位運算子(>>>),它使用了“零擴充套件”:無論正負,都在高位插入0。這一運算子是C或C++沒有的。
  若對char,byte或者short進行移位處理,那麼在移位進行之前,它們會自動轉換成一個int。只有右側的5個低位才會用到。這樣可防止我們在一個int數裡移動不切實際的位數。若對一個long值進行處理,最後得到的結果也 是long。此時只會用到右側的6個低位,防止移動超過long值裡現成的位數。但在進行“無符號”右移位時,也可能遇到一個問題。若對byte或 short值進行右移位運算,得到的可能不是正確的結果(Java 1.0和Java 1.1特別突出)。它們會自動轉換成int型別,並進行右移位。但“零擴充套件”不會發生,所以在那些情況下會得到-1的結果。



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


[b]Java位運算[/b]

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 用高位來代表負數。為避免這個討厭的意外,請記住不管高位的順序如何,它決定一個整數的符號。

[b]4.2.1 位邏輯運算子[/b]
位邏輯運算子有“與”(AND)、“或”(OR)、“異或(XOR )”、“非(NOT)”,分別用“&”、“|”、“^”、“~”表示,4-3 表顯示了每個位邏輯運算的結果。在繼續討論之前,請記住位運算子應用於每個運算數內的每個單獨的位。
表4-3 位邏輯運算子的結果
A 0 1 0 1 B 0 0 1 1 A | B 0 1 1 1 A & B 0 0 0 1 A ^ B 0 1 1 0 ~A 1 0 1 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
[b]
4.2.2 左移運算子 [/b]
左移運算子<<使指定值的所有位都左移規定的次數。它的通用格式如下所示:

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);


}
}

這裡,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 位時,數字被解釋為負值。

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

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 


[b]4.2.4 無符號右移[/b]
正如上面剛剛看到的,每一次右移,>>運算子總是自動地用它的先前最高位的內容補它的最高位。這樣做保留了原值的符號。但有時這並不是我們想要的。例如,如果你進行移位操作的運算數不是數字值,你就不希望進行符號位擴充套件(保留符號位)。當你處理畫素值或圖形時,這種情況是相當普遍的。在這種情況下,不管運算數的初值是什麼,你希望移位後總是在高位(最左邊)補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[]) {
進製表示。注意右移後的值與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 

[b]
4.2.4 無符號右移 [/b]
正如上面剛剛看到的,每一次右移,>>運算子總是自動地用它的先前最高位的內容補它的最高位。這樣做保留了原值的符號。但有時這並不是我們想要的。例如,如果你進行移位操作的運算數不是數字值,你就不希望進行符號位擴充套件(保留符號位)。當你處理畫素值或圖形時,這種情況是相當普遍的。在這種情況下,不管運算數的初值是什麼,你希望移位後總是在高位(最左邊)補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[]) {
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

相關文章