零、前言
本文由int轉byte數組這樣的題目代碼引發的思考,其中涉及到多個讓我混淆的地方。
直接上代碼
public byte[] toBytes(int number){
byte[] bytes = new byte[4];
bytes[3] = (byte)number;
bytes[2] = (byte) ((number >> 8) & 0xFF);
bytes[1] = (byte) ((number >> 16) & 0xFF);
bytes[0] = (byte) ((number >> 24) & 0xFF);
return bytes;
}
如果對這段代碼瞭然於胸,原理思路清洗,就可以不用閱讀本文了。如果對這段代碼的原理不是那麼清晰,那麼很可能是某個基礎知識模糊遺忘了。
我們知道int強轉成short,甚至char。但byte數組給人的感覺就是二進制的東西,好像和那些基本類型“不在一個維度上”,甚至想象不出轉換出來是個什麼效果,能打印出來嗎。。。之所以會有這些疑問,因爲我們對byte的一些基本概念混淆了,或許是從來沒真正的理解透過。
一、先思考幾個基礎問題
問題一:byte是java基本類型嗎,如果是,那它屬於哪一類基本類型
問題二:byte是計算機中最小的單位嗎,char呢? 能清晰的想出來java裏8種基本類型的字節長度以及取值長度嗎
問題三:我們知道java四個整數類型可以相互強轉,比如,int轉byte是怎麼做的
問題四:如何理解程序中的& 0xFF是否必要,可否去掉
問題五:把int切成四個byte,放在byte[]裏,順序怎麼擺放
問題六:我們應該學過“原碼”、“反碼”,“補碼”,那麼java的基礎數值類型在計算機裏是用的補碼嗎
以上這些問題先不要看答案,自己是否能很清晰的回答出來。
問題一答案
byte是java基本類型,而且是整數型(它不止可以用來表示二進制數據,還能表示數字哦)。
工作中我們會經常用byte數組,基本都是在涉及IO流傳輸數據相關代碼中使用。也許就是用的場景太單一順手了,byte給我們的感覺就是二級制的數據流,漸漸的就忽略了 這個類型居然和int之類 是一類東西,它的大小範圍是-128~127,是不是好像在哪聽過這個範圍。是short嗎,答案是否定的。這個範圍還出現在Integer使用地址比較相等的時候,因爲Integer會把-128-127範圍內的數字放入常量池,我們可以直接使用==來比較是否相等。
問題二答案
byte 名字節
,大該是因爲他的英文名字聽起來好像叫“比特”,和bit很像,然後又經常被用在數據流中,給人感覺就好像這是一個不可分割的最小單位。但實際上byte佔8位,即 1byte = 8 bit。所以不是計算機中最小單位。
也許你在用數據庫的時候,使用過bit這個數據類型,它只有true和false兩個值(也就是1和0),這個單位我們可以稱之爲“位”,也就是我們所謂的最小單位。但不要和byte搞混了,他們之間差了8倍。 char就更大了,java中佔兩個字節,c語言中佔一個字節。
8種基本類型字節數以及取值範圍
類型 | 字節大小 | 取值範圍 |
---|---|---|
byte | 1 | -(2^7) ~ (2^7) - 1 |
short | 2 | -(2^15) ~ (2^15) - 1 |
int | 4 | -(2^31) ~ (2^31) - 1 |
long | 8 | -(2^63) ~ (2^63) - 1 |
char | 2 | - |
boolean | 4或1 | true,false |
float | 4 | 6位有效數字 |
double | 8 | 15位有效數字 |
在這張表裏,你可能會對boolean這個類型產生疑問。對於本題答案,你甚至會想byte和boolean是不是有什麼關聯(你可能隱約感覺兩者都是用來表示0和1的)。 當然這是完全錯的,即使是bit和boolean也沒什麼聯繫,boolean在java中也不是隻佔1位。因爲java中並沒有規定boolean的表示方法,實際中boolean佔了4字節(和int一樣大),boolean[]中bolean元素則佔1字節,所以上面表格裏寫4或1。
問題三答案
首先注意這裏是說int強轉成byte,而不是本文所說的int轉byte[]。通過前一個問題我們知道int佔32位,而byte佔8位。比如int a10 = 261對應的二進制就是100000101【左邊剩下的23個0就省略了】,想轉成byte也很簡單,就是直接截取最右邊8位即可:00000101【十進制的5】,也就是261強轉byte後等於5。
問題四答案
十六進制0xFF 也就是二進制的11111111, &運算的作用就是把兩個二進制右邊對齊,然後逐個&運算,只有兩者都是1時結果纔是1,
等效於截取右邊8位二進制數。通過問題三,可知int強轉byte就是簡單截取右邊8位,沒有必要再& 0xFF。 所以上面的代碼可以這麼寫
public byte[] toBytes(int number){
byte[] bytes = new byte[4];
bytes[3] = (byte)number;
bytes[2] = (byte) (number >> 8);
bytes[1] = (byte) (number >> 16);
bytes[0] = (byte) (number >> 24);
return bytes;
}
問題五答案
通過理解這段代碼我們知道,int的32位二進制,最終被截斷成4段,然後把這4段以byte的形式放入byte數組。如下所示
也許會有疑問,爲什麼不是這樣
因爲在二進制截取、排序的過程中 不要帶入“意義”。也就是說,在排這些二進制段的時候不要想着這段二進制原本是什麼意思(比如這裏就不要想着這32位二進制是一個int數字),可以簡單認爲,這就是一串毫無意義字符串。而這串字符串就是從左往右讀的,左邊是開始,右邊是結束。
另一個更準確的理解方式是:通過一個實際的例子畫出圖就能很容易看出
如果把00000101放在b0中,那麼這個byte[]無論正着讀還是反着讀,二進制數字都是亂的。
問題六答案
首先給出答案:是的。 如果這個問題這麼問:計算機中是使用原碼還是補碼錶示數字,基本大家都會答:補碼。這個問題之所以這麼問,就是看概念是否清晰準確。如果關於補碼的概念模糊的話,甚至可能會覺得補碼應用於計算機內的所有信息,整數是不是用的補碼反而不確定了。 下面就複習一下關於補碼的知識:
上面說了,byte的取值範圍是-128~127,這個範圍我們應該不陌生,但爲什麼呢,爲什麼不是“兩頭對稱”的呢,這就是補碼造成的。
在問題三答案
,我們明白了int是如何轉成byte的,例子大家應該也都能看明白。現在換一個例子int a = -216,二進制爲1111111111111111111111111111111111111111111111111111111011111011,同樣截取後8位,結果是11111011,表示十進制的-5。如果對這個計算過程以及結果有點意外,說明對補碼認識不足。下面就以byte類型(8位二進制)爲例,講解一下補碼 首先說一下概念及結論:
- java中的數字都是有符號的,即有正有負
- 有符號數字表示的二進制中左邊第一位是符號位,0表示正數,1表示負數
- 原碼:8位有符號的二級制的原始表達方式
- 反碼:原碼符號位不變,其他位取反(就是0變1,1變0)
- 補碼:反碼+1
- 計算機中的數字使用補碼錶示
- 使用規則: 正數的原碼和補碼一樣(也就不存在反碼),負數的反碼根據上面的規則計算,即反碼+1
爲什麼產生反碼
- 原碼中的 0000000【+0】 1000000【-0】
有正0和負0,這兩個本來一樣的數卻有兩個不同的表示方式 - 原碼的符號位不方便計算,而補碼卻能巧妙的把符號位帶着一起做加法運算(這個就不細講)。
根據上面的規則,byte的256個數字對應關係如下
二進制表示 | 00000000 | 0000001 | … | 01111111 |
---|---|---|---|---|
無符號十進制數字 | 0 | 1 | … | 127 |
補碼十進制數字 | 0 | 1 | … | 127 |
續
10000000 | 1000001 | … | 11111111 |
---|---|---|---|
128 | 129 | … | 255 |
-128 | -127 | … | -1 |
函數圖如下
通過表格以及函數圖,很明顯看出byte的表示範圍是-128 ~ 127【y軸。雖然畫的是連續直線,其實是離散點】
二、總結
本程序簡單講就是:把32位的int像“切甘蔗”一樣平均切成4段byte,從左至右依次放入byte[]。 前提是要理解byte、int等基本類型的本質,以及補碼的作用及使用方式。懂了這些原理,不僅僅可以解決int轉byte數組,反過來轉換也是比較容易理解的。