二進制是計算機的基礎,計算機只識別二進制數據,其基礎運算是采用2進制。編程語言寫好的程序經過編譯后變成計算機能識別的2進制數據,人不可能直接寫2進制數據,其中間需要通過編程語言進行協調,所以編程語言就是連接人類和計算機之間的橋梁,下面補充學習二進制基礎知識。
二進制
(1)計算機內部只有2進制數據,別的一概沒有。Java編程語言,利用算法支持10進制,使用戶感受上可以使用10進制,比如System.out.println(50)運行輸出了50,其實其底層調用了Integer.toString()方法進行了轉換,將2進制110010轉換成10進制50。底層算法是如下方法:
(2)Java底層是有方法實現2進制數據和10進制數據的相互轉換,具體如下:
將10進制字符串轉換為2進制int ---->Integer.parseInt()
將2進制int轉換為10進制的字符串 ---->Integer.toString()
了解2進制規則時,需要配套了解16進制規則,因為兩者可以相互轉換,4位2進制可以簡寫為一個16進制數,可以通過排列組合就可以理解了。
(3)2進制規則:
①逢二進一的計數規則,可以參考只有2個算珠的算盤,移動算珠到上面代表0位,移動算珠下來代表1位
②權:64,32,16,8,4,2,1 (1的2倍數)
③基數:2
④數字:01
(4)16進制規則:
①逢十六進一的計數規則
②權:256,16,1(1的16倍數)
③基數:16
④數字:0123456789abcdef
二進制補碼
計算機用來處理(有符號數)負數問題的算法
補碼算法:位數補碼
①總的位數是4位數
②如果計算結果超過4位數,自動溢出舍棄
先來看看只有4位的2進制數,發現范圍是從0000~1111,代表10進制的0~15,如果1111加1,會變成10000,超過4位,最高位1自動溢出舍棄,變成0000,發現又回到起點,所以如果只是4位的2進制數字,采用補碼規則,最多只能表示16個數,現在前人根據這樣,將2進制高位為0的分成1半,2進制高位為1的分成1半,前者用來表示正數,代表0~7,后者用來表示負數,代表-1~-8。具體參考如下圖片:
其中0~7的對應的2進制數為0000~0111,其中-1~-8的對應2進制數為1111~1000,其實計算機原本只有正數,-1~-8的這幾個,在計算機底層還是正數,通過算法Integer.toString(),變成負數展示給人看,讓人感覺好像有負數,上面通過4位來表示一個數,取值范圍就是-2^3~2^3-1 ,這些數是完全不能滿足生活需求的,因此就擴大了位數,用來展現更多的數字,就有了如下幾種數據類型:
①byte,通過8位來表示一個數,取值范圍為-2^7~2^7-1,取值范圍-128~127,單位為1個byte,即一個字節
②short,通過16位來表示一個數,取值范圍為-2^15~2^15-1,取值范圍-32768~32767,單位為2個字節,即2byte
③帶符號int,通過32位來表示一個數,取值范圍為-2^32~2^31-1,取值范圍-2147483648~2147483647,單位為4個字節,即4byte
④無符號int,也是32位,取值范圍0~2^32-1
⑤long,通過64位來表示一個數,取值范圍-2^63~2^63-1,取值范圍-9223372036854774808~9223372036854774807,單位為8個字節,即8byte
以下為浮點型:
(1)float,4byle,32位
(2)double,8byte,64位
字符型:
(1) char,2個字節,16位
布爾型:
(1)boolean,可能就是一個bit,也可能是一個byte
互補對稱公式:-n=~n+1,意思就是一個數對應的另外一個符號對稱數,等於2進制位數取反后加1。可以參考上圖圓盤就一目了然。
@Test public void testBinary8() { /** * 測試互補對稱公式 -n=~n+1 */ System.out.println(~8+1);//-8 System.out.println(Integer.toBinaryString(~8+1));//11111111111111111111111111111000 System.out.println(~8);//-9 System.out.println(Integer.toBinaryString(~8));//11111111111111111111111111110111 System.out.println(~-8);//7 System.out.println(Integer.toBinaryString(~-8));//111 System.out.println(~-8+1);//8 System.out.println(Integer.toBinaryString(~-8+1));//1000 }
二進制運算符
① ~ 取反
② & 與運算 主要用於截取
0&0=0
0&1=0
1&0=0
1&1=1
③ | 或運算 主要用於合並
0|0=0
0|1=1
1|0=1
1|1=1
④ >>> 邏輯右移動運算
將二進制數值總體往右邊移動一定位數,低位溢出不要,高位不夠用0補齊
比如 n=00000000 00000000 00000000 11010001
m=n>>>1運算后
變成 (0)00000000 00000000 00000000 1101000(1)
整體往右邊移動了1位,低位的1去掉,高位補上0,變成如下形式:
000000000 00000000 00000000 1101000
⑤ >> 數學右移動運算
⑥ << 數學左移動運算
編碼方案
字符是16位的,而流(文件/互聯網)按照byte(8位)進行處理,將文字進行傳輸必須拆分為byte,這個拆分方法,成為文字的編碼。把文字拆開,變成8位8位的格式,即編碼,反過來叫做解碼。
編碼方案學習:
首先需要了解一下幾個概念,unicode,ASCII,UTF-8之間到底有啥區別?
unicode:一個字符對應一個數,就是將全世界所有知道的字符統計進去,目前到了10萬+
UCS unicode3.2標准 0~65535 java char就是這個UCS
UTF-8:變長編碼,1-4字節,短的1字節,長的4字節,具體有以下幾種:
① 0~127 1字節編碼,這個范圍的也叫做ASCII碼,主要顯示英文和其他西歐語言,格式為 0XXXXXXXX
② 128~2048? 2字節編碼,格式為110XXXXX 10XXXXXX
③ 2048?~65535 3字節編碼,中文,韓文日文等都在這個范圍內,格式為1110XXXX 10XXXXXX 10XXXXXX
④ 65535~10萬 4字節編碼,格式11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
比如'中'這個字符,對應的unicode編碼為0x4e2d,轉化成二進制為0100 1110 0010 1101
然后需要使用三個字節編碼,將二進制格式按順序填充到格式1110XXXX 10XXXXXX 10XXXXXX中,變成11100100 10111000 10101101,這個就是UTF-8編碼,所以將字符的unicode索引添加到UTF-8的字節位置上,叫做編碼,然后網絡通過8位一個字節一個字節的傳輸,然后如果將網絡上傳輸的字節一個一個的組合起來,反過來從其中取出對應位置的的數,再和unicode索引對照找到對應的字符,這個過程就做解碼。
編碼實例分析
接下來用'中'字符進行編碼 中字符 unicode編碼為:0100 1110 0010 1101 ,在java中會將前面的位補齊,變成如下形式
00000000 00000000 01001110 00101101
然后需要編碼的格式為:
1110XXXX 10XXXXXX 10XXXXXX
一般按照3部分進行編碼,按照先后順序分別是b1 b2 b3
step 1
先得到b3,b3需要得到中字符unicode編碼的最后6位,需要使用與運算符,截取最后6位,使用如下字符進行與運算 00000000 00000000 00000000 00111111,運算先轉換成16進制再進行運算,這個目的就是截取了unicode最后6位然后還需要將b3的前面兩個已經規定好的10給添加到得到的結果的前面,使用或運算符 剛得到的與運算結果應該是00000000 00000000 00000000 00101101。使用或運算符,將10添加上,需要還進行或運算的值為 00000000 00000000 00000000 10000000,換算成16進制為0x80。
所以b3的最后結果為(0x4e2d&0x3f)|0x80
step2
然后需要得到b2,b2部分也需要截取6位,但是截取的是unicode字符剛截取部分的前面6位,這個時候如果使用邏輯右移動運算符,可以將需要截取的又放到最后6位,又可以按照剛才的方法進行截取和拼接了,先邏輯右移動6位
0x4e2d>>>6
然后再截取最后6位,使用與運算
(0x4e2d>>>6)&0x3f
最后再將10拼接到前面,使用或運算符
((0x4e2d>>>6)&0x3f)|0x80
step3
最后得到b1,可以參考前面的方法, 先邏輯右移動12位,使用邏輯右移動運算
0x4e2d>>>12
然后再截取最后4位,使用與運算
截取4位運算值為00000000 00000000 00000000 00001111
(0x4e2d>>>12)&0xf
再將1110拼接上去
拼接運算值00000000 00000000 00000000 11100000
((0x4e2d>>>12)&0xf)|0xe0
解碼實例分析
如果是解碼,就是把剛才編碼得到的三個字節b1 b2 b3,取出編碼時存入的部分,再重新按順序拼接
b1需要取出最后4位
b1&0xf
b2需要取出最后6位
b2&0x3f
b3需要取出最后6位
b3&0x3f
最后在將取出的部分拼接起來,注意,b1需要左移動12位,b2需要左移動6位,b3不要移動
((b1&0xf)<<12)|((b2&0x3f)<<6)|b3&0x3f
最終將得到'中'字符對應的unicode編碼
以下是編碼解碼過程代碼:
@Test public void testBinary9() { /** * 打印出中這個字符的unicode索引的二進制形式 */
int i='中'; System.out.println(Integer.toBinaryString(i));//100111000101101 省略了最前面的0 //其實就是0100 1110 0010 1101,變成16進制為4e2d
} @Test public void testBinary10() throws UnsupportedEncodingException { /** * 將'中'字進行編碼,變成UTF-8編碼 * step1 先獲得UTF-8的b3部分,使用&和|運算 */
int a='中'; int m=0x3f; int n=0x80; int b3=((a&m)|n); System.out.println(Integer.toBinaryString(a));//100111000101101
System.out.println(Integer.toBinaryString(a&m));//101101
System.out.println(Integer.toBinaryString(b3));//10101101
/** * step2 再獲取UTF-8的b2部分 使用>>>,&,|運算 */
int k=a>>>6; System.out.println(Integer.toBinaryString(k));//100111000
int b2=(k&m)|n; System.out.println(Integer.toBinaryString(b2));//10111000
/** * step3 再獲取UTF-8的b1部分,使用>>>,&,|運算 */
int s=a>>>12; int o=0xf; int p=0xe0; int b1=(s&o)|p; System.out.println(Integer.toBinaryString(b1));//11100100 //JDK提供了UTF-8到char的解碼
byte[] bytes= {(byte) b1,(byte) b2,(byte)b3}; String result=new String(bytes,"utf-8"); System.out.println(result);//暫時輸出亂碼
/** * 也可以手動解碼,將剛才得到的三個字節按照編碼方式,將放到對應位置的bit截取后重新拼接 */
//截取b3最后6位
int c1=b3&0x3f; //截取b2最后6位,並左移動6位
int c2=(b2&0x3f)<<6; //截取b1最后4位,並左移動12位
int c3=(b1&0xf)<<12; //拼接
int cResult=c3|c2|c1; System.out.println((char)(cResult)); }
移位運算符的數學意義
十進制
移動小數點運算
123456. 小數點向右移動
1234560. 小數點向右移動1次,數字*10
12345600. 小數點向右移動2次,數字*10*10
如果小數點位置不變,數字向左移動
123456. 數字向左移動
1234560. 數字向左移動1次,數組*10
12345600. 數字向左移動2次,數組*10*10
二進制依次類推
移動小數點運算
000000000 00000000 00000000 00110010. 小數點向右移動
00000000 00000000 00000000 001100100. 小數點向右移動1次,數字*2
0000000 00000000 00000000 0011001000. 小數點向右移動2次,數字*2*2
如果小數點位置不變,數字向左移動
000000000 00000000 00000000 00110010. 數字向左移動
00000000 00000000 00000000 001100100. 數字向左移動1次,數組*2
0000000 00000000 00000000 0011001000. 數字向左移動2次,數組*2*2
數學右移位運算
1 相當如將原數據進行除法,結果向小數點方向取整數
2 >>數學移位:正數高位補0,負數高位補1,有數學意義
3 >>> 邏輯移位,不管正數負數,高位都是補0,沒有數學意義
n=11111111 11111111 11111111 11110111 使用互補對稱公式得出為-9
n>>1變成-5
n=111111111 11111111 11111111 1111011 使用互補對稱公式得出為-5
如果n>>>1
n=011111111 11111111 11111111 1111011 非常大的一個數
部分經典面試題
(1)經典面試題:
int i=0x32;
System.out.println(i); //3*16^1+2*16^0=50
(2)經典面試題:
int n=0xffffffff;
System.out.println(n);//使用互補對稱公式得出-1
(3)經典面試題:
正數的溢出為負數,負數的溢出為正數
答案:錯,根據圓盤轉圈,只能說不一定,要看溢出多少了,所以都有可能
(4)經典面試題:
System.out.println(~8+1);
答案:-n=~n+1公式,得到-8
(5)經典面試題:
如何優化n*8計算?
答案:n<<3; //左移位比乘法快
總結:二進制部分為計算機基礎知識,有必要補充學習,加深對基礎知識的理解。
參考博客;