在使用Java進行網絡編程時,常常需要進行進制轉換,而在進行這類操作時,往往需要對進制與Java數據類型有較深入的理解,才能確保在編程時不會出現錯誤。同時,深入瞭解進制能寫出更加高效的代碼。
本文先從計算機進制的兩個基本概念入手,帶讀者簡單的瞭解計算機進制的原理,然後再對Java數據類型和數據之間的轉化展開論述,最後纔對數據類型間的進制轉化做詳細說明。
本文由淺入深,適合初學者和進階者閱讀。
符號位 和 補碼
在計算機中,分有符號和無符號的兩種數據類型,無符號的數據類型只能表示正數,而有符號的數據類型則可以表示負數。爲了能將數據表示爲負數,在有符號的數據類型中,會將二進制數的第一位作爲符號位,0表示正數,1則表示負數。通過判斷第一位的值,計算機就能判斷這個數是正數還是負數。
Java不像c/c++這樣的語言,不能聲明無符號的數據類型,所以Java的所有數據類型都是有符號的。
什麼是補碼?
補碼是計算機中用來表示負數,使得負數能夠使用加法器參與加法運算的一種碼。
在計算中,正數採用原碼存儲,原碼即爲本身數值,而負數採用補碼的方式進行存儲,這樣做的目的是方便計算機運算,降低電路設計的複雜性。
如何快速求得負數補碼?
補碼的轉換規則爲符號位不變,其餘逐位求反再加1
關於補碼和符號位在此我不進行詳盡的解釋,因爲超出了本文的論述範圍,可以閱讀我的博客 二進制補碼計算原理詳解 進行了解,博客地址:https://blog.csdn.net/zhuozuozhi/article/details/80896838
特別說明一點,符號位是可以直接參與運算的。
示例:使用二進制表示以下數值
byte positiveNumber = 19 ; // 0 0010011
byte negativeNumber = -45;// 1 1010011 (【原】1 0101101 -> 【反】1 1010010 -> 【補】1 1010011 )
在上面的例子中,數據類型爲byte,所以佔一個字節,即8 bit,其中1位充當符號位,那麼還有七位充當數據位,所以byte的取值範圍從0~255變成-128~127。19爲正數,所以二進制數第一位爲0;而-45爲負數,所以它的符號位爲1,在計算機中採用補碼的形式存儲負數。-45的負數原碼爲 1 0101101,按照補碼轉換規則,即求得負數補碼爲 1 1010011
Java數據類型
在說明數據類型之前,筆者要特別說明一點,Java是一門面向對象的高級編程語言,他爲一般高級編程提供便利性,所以隱藏了底層的一些細節,這才導致底層相對不容易被操作和使用。筆者之所以強調數據類型與數據類型的轉換規則,是爲講解進制轉換規則做好準備。
隨便翻開一本Java入門書籍,裏面都有基本類型的的佔用字節和取值範圍。最好記住這些內容。如下圖是摘自《Java程序設計》的一張數據類型表。
數據的表示範圍大小從小到大的排序爲:byte < short < int < long < float < double
需要注意的是:float 和 double是實數類型的數據,雖然只佔用四個字節和8個字節,但是他們的數據類型中存儲了小數點、指數、有效位數及其他一些數據,所以他們的取值範圍遠大於相同字節數的int和long類型。
數據類型之間的轉換規則
在進行數據類型轉換的時候有如下幾種情況:
數據範圍小的數據向數據範圍大的數據轉換時,無需要進行特殊轉換,數值保持不變
示例:byte類型向short類型轉換
byte b = 64;
short s = b; //數值不變,不報錯
如果數據範圍大的數據類型數據範圍小的數據類型轉換時,小範圍的數據可能無法表示該數值,所以需要強轉
示例 : short類型向byte類型轉換
short s = 125;
byte b = (byte)s; // b = 125; 125在數據範圍內,沒有變化
short s2= 129;
byte b = (byte)s; //b = -127; 129不在byte數據範圍內,數值被強制轉換,與原數據不一致。
分析上面例子的兩個強轉,由於byte的數值表示範圍在-128 - 127,所以s 的值在範圍內,能夠順利強轉不出錯,而s2卻出現偏差,這就不得不瞭解它們的轉換規則。
首先我們分析一下爲什麼short類型的129轉化成byte類型,爲什麼會變成-127?觀察short類型的129,short佔兩個字節,即16位,他的的二進制表示爲 0 0000000 10000001 , 當我們強制轉化成byte時,由於byte只有8bit,這就導致數據位溢出,short只能存儲16位中的8位,Java會自動捨棄前面8位,保留後面8位,所以強轉後就變成了 1 0000001。由於在Java中,第一位爲符號位,這就導致了這個數強轉之後被識別成了一個負數。而負數在符號位當中是以補碼的形式存儲,系統認爲這是一個負數補碼,我們從這個補碼反求原碼來確定整個負數的數值是多少,【補】1 0000001 -> [反] 1 0000000 -> [原]1 1111111 得到原碼 1 1111111 ,所以這個數值才被強轉爲-127。
這是Byte類型轉換圖
byte類型的取值範圍爲-128~127,當某個數值到達上限127時,若再+1則會變成-128,週而復始。
當一個數值大於byte類型時,若要將他強轉爲byte類型時,則需要遵循此循環。
題外補充:浮點類型與整數的相關強制轉換
我們知道,Int類型的最大值是4個字節31位表示的最大數,即 2147483647;如果浮點類型的數值在Integer之內,那麼會直接省略小數部分,而當浮點數大於Integer範圍的時候,會有一些不同。
示例: 浮點類型轉long類型,int類型
double d = 2147483649.123d; //一個略大於Int類型表示範圍的數
long l = (long)l;//l = 2147483649 //實際上,double轉long類型,只要不超過Long類型表示的最大數,都是省略小數點以後取整即可
int i = (int)d; //2147483647
注意,當浮點類型的示數超過int 類型上限時,i就會固定取int 類型的最大值
byte b = (byte)d;//b=-1,
浮點轉byte類型時,先將double轉成int,再從int轉到byte,當double超過Int上限時int會取最大值,再轉化爲byte時即固定爲-1,short類型也是類似。
數據類型之間轉換的二進制規則
上一節中已經詳細論述了Java的數據類型轉換規則,在這一節,將從二進制角度看數據轉換是如何進行的。
數據範圍小的數據向數據範圍大的數據轉換時,數值保持不變,那麼二進制數是如何變化的?
示例:正數byte類型向short類型轉化
byte b = 64; // 二進制表示: 0 1000000
short s = b; // 二進制表示: 0 0000000 01000000
正數byte在向short類型轉化時,s的高地址位會自動補0
例3:byte類型負數向short數據類型轉化
byte b= -64 // 二進制表示: 1 1000000 = -64
short s = b //二進制表示: 1 1111111 11000000 = -64
在這個例子中,-64爲負數,s的高位地址會自動補1,確保s反求原碼還等於-64
負數向範圍更大的類型轉換時,原理上是先求負數原碼,在數據高位上填充0,擴充到固定的數據類型寬度,然後再求補碼。
在實際編程中,這個隱式轉換會帶來很多問題,比如你想把byte類型數據當做無符號類型使用,需要將無符號數據放在int數據類型中計算,但是Java並不知道,會在前面自動填充的1可能會導致計算出錯。好在Java包裝類中提供了向上無符號轉型的方法
示例:利用Byte.toUnsignedShort()獲取向上轉型獲得無符號Short數據
byte b = -45// 1 1010011
short s1 = b; //1 1111111 11010011
int s2 = Byte.toUnsignedInt(b); // 得到 0 0000000 11010011 = 211
如果在使用過程中想要向上轉型,想得到無符號位的數據,那麼就需要使用Byte提供的toUnsignedInt(),其他包裝類也有提供向上無符號轉型的方法。當然,熟練的可以不使用這些方法,直接進行位運算就好,這也是我推薦的。
如果數據範圍大的數據類型數據範圍小的數據類型轉換時,直接截斷高位數據
示例:short類型的129強轉爲byte類型
short s = 129;
byte b = (byte)s; // b = -127
當進行強制轉化時,s的數據高位都被捨棄,只留下 10000001,因爲最高位爲1,計算機判定爲負數補碼,補碼10000001表示 -127,所以就是強轉後就是-127。
最後,來講下Java二進制字節級操作及位級操作的常用操作。
再次強調,由於Java數據類型沒有提供無符號的數據類型,所以在進行這類操作時務必要注意數值的取值範圍。
將數值轉爲二進制、八進制數、十六進制、任意進制字符串
int i = 100;
Integer.toBinaryString(i); //轉二進制 = 1100100
Integer.toOctalString(i);//轉8進制 = 144
Integer.toHexString(i);//轉16進制 = 64
Integer.toString(int value,int radix); //轉任意進制
使用使用有以下幾點注意點
1. short和byte沒有提供轉進制字符串,實際上也不太需要,如果byte和short需要打印,可以先強轉爲int類型,使用Integer的方法進行打印,但是爲負數的時候打印可能會有偏差,需要注意。
2. 打印時高位不會補0,比如打印int類型的二進制字符串,高位不會補0,需要自己格式化輸出
從二進制、八進制、十六進制、任意進制字符串轉到指定數值
1.幾乎每個包裝類型下面都有一個 valueOf(string value,int radix) 方法,可以使用這個方法轉化
2.進制字符串的前綴(0x,0b..)不需要帶,帶了會報錯。
格式化輸出進制數
int i = 10;
String.format("%02x",i); // 0a
16進制可以直接補充,二進制需要自己實現
向上無符號轉型
示例:byte通過方法向上轉型獲取int類型
Byte.toUnsignedInt(b); //其他包裝類類似
示例: byte通過位運算向上轉型獲得short數據
byte b = -45; //1 1010011
short s = (short)(((int)b) & 0xff) //211
解析,Java中的類型轉化不太方便,因爲默認數值類型是int,所以所有的類型轉化都要先轉成int,再強轉爲自己想要的類型,在本例中,b爲byte類型,且爲負數,b強轉爲int類型的二進制表示爲 1 1111111 11111111 11111111 11010011, 數值 0xff的二進制表示爲 0 0000000 00000000 00000000 11111111,兩者通過與位運算,
1 1111111 11111111 11111111 11010011
0 0000000 00000000 00000000 11111111 &
得
0 0000000 00000000 00000000 11010011 //即211
再通過將至轉換爲short,截斷前16位,得到
0 0000000 11010011 , 得211
讀取兩個byte數值轉化爲一個short類型數據
byte b = -45; //1 1010011
byte b2 = 11; //0 0001011
short s = (short)(((int)b & 0xff << 8) | ((int)b2 & 0xff)) ; //-11509
解析, 首先通過(int)b & 0xff 獲得整數值211,二進制數表示爲 0 0000000 00000000 00000000 11010011,然後對這個數進行左移位8位
0 0000000 00000000 00000000 11010011 << 8 ,得 0 0000000 00000000 11010011 00000000,再計算 ((int)b2 & 0xff) 得 0 0000000 00000000 00000000 00001011
將兩者進行或位運算
0 0000000 00000000 11010011 00000000
0 0000000 00000000 00000000 00001011 |
得
0 0000000 00000000 11010011 00001011
最後轉爲short截斷前16位,算得
1 1010011 00001011,即-11509
實際上,在位運算中,看十進制數沒意義,因爲二進制運算的結果在十進制什麼都看不出來
判斷二進制數的某位是0還是1
示例:判斷byte數據的第五位是0還是1
byte b = -45; //1 1010011;
boolean bool = (b & 0x10) != 0
解析:0x10的二進制數表示爲 0001 0000
00000000 00000000 00000000 11010011
00000000 00000000 00000000 00010000 &
得
00000000 00000000 00000000 00010000 //16
通過與位計算,0x10除了第五位是1,其他位都是0,那麼其他位進行與位運算,都會變成0,而第五位如果是1,那麼得1,否則爲0,而如果所有位都爲0,那麼所得結果爲0,否則就是有值,以此來判斷該位是否有值
思考:如果某數據類型判斷的某兩位爲1還是0,該如何做,這個留給讀者自己思考吧
補充:
十六進制,二進制如何快速轉化?
示例:十六進制、二進制快速轉化技巧
0101 1010 轉16進制
一位十六進制對應4位二進制數,比如上面的二進制數,前四位得到的結果是 5,而後四位得到的數值爲10,即a,所以這個數的十六進制表示數爲 0x5a,反之,十六進制轉二進制就是把一位16進制數轉成4位二進制數。
位運算是如何進行的?
位運算就是將前後兩個數值的每個位逐一進行布爾運算,算得想要的數值
示例:與位運算
0010 0010
1110 0001 &
上下對應逐一進行布爾運算,得
0010 0000
本文純屬個人理解,如有紕漏,請勿拍磚。