Java進制轉換原理詳解

在使用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 

本文純屬個人理解,如有紕漏,請勿拍磚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章