引入
二進制是計算機的基礎,追根溯源還是因爲Si的半導體性。
除了二進制,還有十六進制,它是簡化二進制的表示。
做個測試:
@Test
public void testHex() {
int n = 0x77d45d25;
System.out.println(
Integer.toBinaryString(n));
}
0x
表示這是一個十六進制數。
結果:
1110111110101000101110100100101
我們自己轉換一下:
控制檯輸出的結果它把最高位的0給舍掉了,但這個不影響。
爲什麼要有補碼
先看0000到1111的二進制數:
十進制代表的就是0到15。
那這裏爲什麼要畫成一個圈呢?因爲有溢出的概念。
我們等下會測試。
現在的問題是:怎麼纔能有負數?
解決的辦法就是,把0到15這16個數分一半給負數:
這樣就ok了。我們終於有負數了。
這麼一劃分出現了一個神奇的事情:
這個圈圈上的任意一個數,它的二進制取反之後,再加一(順時針移動一次),會變成原數的相反數!
比如2,它的二進制是0010
。
取反:1101
加一:1110
在圖上看看,1110
不就是-2嗎?
拿代碼測一下:
@Test
public void testInverse(){
System.out.println(~100+1);
}
結果:
-100
特殊的值
我認爲的補碼就是因爲負數而產生的。
我們觀察上面的圖,注意幾個特別的數:
在四位的情況下:
0—>0000
-1—>1111
最小值 —> 1000
最大值 —> 0111
你需要記住的是,任何位數,-1永遠都是一羣1。
@Test
public void testMore() {
int n = -9;
System.out.println("-9的二進制:" + Integer.toBinaryString(n));
n = -1;
System.out.println("-1的二進制:" + Integer.toBinaryString(n));
int max = Integer.MAX_VALUE;
int min = Integer.MIN_VALUE;
System.out.println("最大的整數: " + max);
System.out.println("最小的整數:" + min);
System.out.println("最大整數的二進制: " +
Integer.toBinaryString(max));
System.out.println("最小整數的二進制: " +
Integer.toBinaryString(min));
}
結果:
-9的二進制:11111111111111111111111111110111
-1的二進制:11111111111111111111111111111111
最大的整數: 2147483647
最小的整數:-2147483648
最大整數的二進制: 1111111111111111111111111111111
最小整數的二進制: 10000000000000000000000000000000
這裏是32位,和我們探究的4位理論上是一樣的。
那麼,如何才能快速知道-9的二進制表示呢?
首先,你知道-1是32個1(11111111111111111111111111111111)
然後,因爲-9是-1減掉8,所以你用32個1去減8(1000)
另外一個有趣的地方就是:
最大值加一變成最小值。
這個是正常現象,不能叫做溢出。
我們拿8位的byte做個例子:
@Test
public void testbyte() {
byte b = 127;
byte ans = (byte) (b + 1);
System.out.println(ans);
}
結果:
-128
溢出
還是看4位的情況。
1111
如果還要加1,就會溢出。
溢出自動捨去高位:
於是結果就是0000
。
溢出保證我們的整個邏輯還是正確的。
數學移位
什麼是數學移位?
就是>>
以及<<
。
它是二進制數的移動,所以計算起來非常之快。
當然,你完全可以將其理解爲乘2和除2。
>>
是右移,右移它的數量級就會降低,移一次就會有除以2的效果。
相反,<<
是左移,左移一次就會有乘以2的效果。
看個測試:
@Test
public void testMoveBit(){
System.out.println(50>>1);
System.out.println(50<<1);
System.out.println(50>>2);
System.out.println(50<<2);
System.out.println("------------------------");
System.out.println(-50>>1);
System.out.println(-50<<1);
System.out.println(-50>>2);
System.out.println(-50<<2);
}
結果:
25
100
12
200
------------------------
-25
-100
-13
-200
唯一要注意的就是除不盡的時候要取整。
還有一個很經典的問題,就是右移的時候,高位到底是補0還是補1。
用代碼看一下就知道了:
@Test
public void testMoveBitPositiveAndNegative(){
System.out.println("positive--------------------");
System.out.println(Integer.toBinaryString(50));
System.out.println(Integer.toBinaryString((50>>2)));
System.out.println("negative--------------------");
System.out.println(Integer.toBinaryString((-50)));
System.out.println(Integer.toBinaryString((-50>>2)));
}
結果:
positive--------------------
110010
1100
negative--------------------
11111111111111111111111111001110
11111111111111111111111111110011
正50它把前面的0全部給省略了,我們自己補上:
正數數學右移的時候,就是高位補0,低位溢出。
負數數學右移的時候,從控制檯的結果可以看到,是高位補1,低位溢出。
邏輯位移
邏輯位移只有右移,沒有左移,右移的符號是>>>
。
它的作用很大,源碼中經常出現邏輯右移,主要作用就是用來將數字拆分成一個一個字節的。
@Test
public void testMoveBitLogic(){
System.out.println(50>>>1);
System.out.println(50>>>2);
System.out.println("------------------------");
System.out.println(-50>>>1);
System.out.println(-50>>>2);
}
結果:
25
12
------------------------
2147483623
1073741811
正數的行爲和數學右移很像。但是,它不是用來做數學計算的。所以如果你想要快速地完成除以2的工作,請用>>
。
負數的右移很奇怪,那就是高位補0還是補1的問題了。
測試:
@Test
public void testMoveBitLogicBinary(){
System.out.println("positive--------------------");
System.out.println(Integer.toBinaryString(50));
System.out.println(Integer.toBinaryString((50>>>2)));
System.out.println("negative--------------------");
System.out.println(Integer.toBinaryString((-50)));
System.out.println(Integer.toBinaryString((-50>>>2)));
}
結果:
positive--------------------
110010
1100
negative--------------------
11111111111111111111111111001110
111111111111111111111111110011
正數的時候和數學右移一樣,都是高位補0。
負數的時候,我們睜大眼睛看看:
負數邏輯右移,是高位補0,低位溢出。
邏輯右移的應用
看一下RandomAccessFile
的writeInt
方法:
public final void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
//written += 4;
}
可以看出來,它是一個一個字節寫出去的,從高八位開始。
其實在網絡傳輸中,傳輸的都是字節,所以也會用到邏輯右移。