Java之 無符號類型是怎麼回事

原文地址:http://www.darksleep.com/player/JavaAndUnsignedTypes.html

原文作者:Sean R. Owens

以下是正文


Java 中的無符號類型是怎麼回事兒?

在 C 和 C++ 這樣的語言中,都提供了不同長度的整數類型:charshortintlong (實際上,char 並不是真正的整數,但是你可以把它當成整數來用。在實際應用場景中,很多人在 C 語言中用 char 來存儲較小的整數)。在大部分的 32 位操作系統上,這些類型分別對應 1 字節,2 字節,4 字節和 8 字節。但是需要注意的是,這些整數類型所對應的字節長度在不同的平臺上是不一樣的。相對而言,由於 Java 是針對跨平臺來設計的,所以無論運行在什麼平臺上,Java 中的 byte 永遠是 1 字節,short 是 2 字節,int 是 4 字節,long 是 8 字節。

C 語言中的整數類型都提供了對應的“無符號”版本,但是 Java 中就沒有這個特性了。我覺得 Java 不支持無符號類型這個事兒實在是太不爽了,你想想,大量的硬件接口、網絡協議以及文件格式都會用到無符號類型!(Java 中提供的 char 類型和 C 中的 char有所不同,在 Java 中,chat 是用 2 個字節來表示 Unicode 值,在 C 中,char 是用 1 個字節來表示 ASCII 值。雖然可以在 Java 中把 char 當做無符號短整型來使用,用來表示 0 到 2^16 的整數。但是這樣來用可能產生各種詭異的事情,比如當你要打印這個數值的時候實際上打印出來的是這個數值對應的字符而不是這個數值本身的字符串表示)。

那麼,如何應對 Java 中無符號類型的缺失?

好吧,對於我給出的這種方案,你可能會不喜歡……

答案就是:使用比要用的無符號類型更大的有符號類型。

例如:使用 short 來處理無符號的字節,使用 long 來處理無符號整數等(甚至可以使用 char 來處理無符號短整型)。確實,這樣看起來很浪費,因爲你使用了 2 倍的存儲空間,但是也沒有更好的辦法了。另外,需要提醒的是,對於 long 類型變量的訪問不是原子性操作,所以,如果在多線程場景中,你得自己去處理同步的問題。

如何以無符號的形式存儲和讀取數據?

如果有人從網絡上給你發送了一堆包含無符號數值的字節(或者從文件中讀取的字節),那麼你需要進行一些額外的處理才能把他們轉換到 Java 中的更大的數值類型。

還有一個就是字節序問題。但是現在我們先不管它,就當它是“網絡字節序”,也就是“高位優先”,這也是 Java 中的標準字節序。

從網絡字節序中讀取

假設我們開始處理一個字節數組,我們希望從中讀取一個無符號的字節,一個無符號短整型和一個無符號整數。

short anUnsignedByte = 0; 
char anUnsignedShort = 0; 
long anUnsignedInt = 0; 
int firstByte = 0; 
int secondByte = 0; 
int thirdByte = 0; 
int fourthByte = 0; 
byte buf[] = getMeSomeData(); 
// Check to make sure we have enough bytes 
if(buf.length < (1 + 2 + 4)) doSomeErrorHandling(); 
int index = 0; 
firstByte = (x000000FF & ((int)buf[index])); 
index++; 
anUnsignedByte = (short)firstByte; 

firstByte = (x000000FF & ((int)buf[index])); 
secondByte = (x000000FF & ((int)buf[index+1])); 
index = index+2; 
anUnsignedShort = (char) (firstByte << 8 | secondByte); 

firstByte = (x000000FF & ((int)buf[index])); 
secondByte = (x000000FF & ((int)buf[index+1])); 
thirdByte = (x000000FF & ((int)buf[index+2])); 
fourthByte = (x000000FF & ((int)buf[index+3])); 
index = index+4; 
anUnsignedInt = ((long) (firstByte << 24 
                        | secondByte << 16 
                        | thirdByte << 8 
                        | fourthByte)) 
                        & xFFFFFFFFL;

好吧,現在看起來有一點兒複雜。但是實際上很直觀。首先,你看到很多這樣的東東:

x000000FF & (int)buf[index]

首先,把有符號的 byte 提升成 int 類型,然後對這個 int 進行按位與操作,僅保留最後 8 個比特位。因爲 Java 中的 byte 是有符號的,所以當一個 byte 的無符號值大於 127 的時候,表示符號的二進制位將被設置爲 1(嚴格來說,這個不能算是符號位,因爲在計算機中數字是按照補碼方式編碼的),對於 Java 來說,這個就是負數。當將負數數值對應的 byte 提升爲 int 類型的時候,0 到 7 比特位將會被保留,8 到 31 比特位會被設置爲 1。然後將其與 0x000000FF 進行按位與操作來擦除 8 到 31 比特位的 1。上面這句代碼可以簡短的寫作:

xFF & (int)buf[index]

Java 自動填充 0xFF 的前導的 0 ,並且在 Java 中,位操作符 & 會導致 byte 自動提升爲 int

接下來你看到的是很多的按位左移運算符 <<。 這個操作符會對左操作數按位左移右操作數指定的比特位。所以,如果你有一個 int foo = 0x000000FF,那麼 foo << 8 會得到 0x0000FF00foo << 16 會得到 0x00FF0000

最後是按位或操作符 |。假設你現在把一個無符號短整型的 2 個字節加載到了對應的整數中,你會得到 0x00000012 和 0x00000034 兩個整數。現在你把第一個字節左移 8 位得到 0x00001200 和 0x00000034,然後你需要把他們再拼合回去。所以需要進行按位或操作。0x00001200 | 0x00000034 會得到 0x00001234,這樣就可以存儲到 Java 中的 char類型。

這些都是基礎操作。但是對於無符號 int,你需要把它存儲到 long 類型中。其他操作和前面類似,只是你需要把 int 提升爲 long 然後和 0xFFFFFFFFL 進行按位與操作。最後的 L 用來告訴 Java 請把這個常量視爲 long 來處理。

向網絡寫入字節序

假設現在我們要把上面步驟中我們讀取到的數值寫入到緩衝區。我們當時是按照無符號 byte,無符號 short 和無符號 int 的順序讀取的,現在,甭管什麼原因吧,我們打算按照無符號 int,無符號 short 和無符號 byte 的順序來寫出。

buf[0] = (anUnsignedInt & 0xFF000000L) >> 24; 
buf[1] = (anUnsignedInt & 0x00FF0000L) >> 16; 
buf[2] = (anUnsignedInt & 0x0000FF00L) >> 8; 
buf[3] = (anUnsignedInt & 0x000000FFL); 

buf[4] = (anUnsignedShort & 0xFF00) >> 8; 
buf[5] = (anUnsignedShort & 0x00FF); 

buf[6] = (anUnsignedByte & 0xFF);

字節序到底是怎麼回事兒?

這是什麼意思?我需要關注嗎?以及,網絡字節序什麼樣的?

Java 中所使用的“高位優先”字節序又被稱爲“網絡字節序”。Intel x86 處理器是“低位優先”字節序(除非你在上面運行 Java 程序)。x86 系統創建的數據文件通常是(但不是必須的)低位優先的,而 Java 程序創建的數據文件通常是(但不是必須的)高位優先的。任何系統都可以按照自己需要的字節序來輸出數據。

字節序是什麼意思?

“字節序”是指計算機是按照何種順序在內存中存儲數值的。常見的無非是高位優先和低位優先兩種模式。你當然需要關注字節序的問題了,否則,如果你按照高位優先的字節序去讀取一個低位優先字節序存儲的數據文件,很可能就只能得到亂七八糟的數據了,反之亦然。

任何數值,無論是何種表達方式,比如 5000,000,007 或者它的 16 進制格式 0x1DCD6507,都可以看做是數字字符串。對於一個數字字符串,我們可以認爲它有開始(最左),有結束(最右)。在英語中,第一個數字就是最高位數字,例如 5000,000,007 中的 5 實際上表示的是 500,000,000。最後一位數字是最低位數字,例如 500,000,007 中的 7對應的值是 7

當我們說到字節序的時候,我們是參照我們寫數字時候的順序。我們總是從高位開始寫,然後是次高位,直到最低位,是不是這樣啊?

在上面的例子中,數值 500,000,007,對應 16 進製表示方式是 0x1DCD6507,我們把它分成 4 個獨立的字節:0x1D0xDC0x65 和 0x07,對應 10 進制的值 29, 205, 101 和 7。最高位字節 29 表示 29 *256 * 256 * 256 = 486539264,接下來是 205,表示 205 * 256 * 256 = 13434880,然後是 101,表示 101 * 256 = 25856,最後一個 7 就是 7 * 1 = 7。它們的值:

486539264 + 13434880 + 25856 + 7 = 500,000,007

當計算機在它的內存中存儲這 4 個字節的時候,假設存儲到內存的地址是 2056, 2057, 2058 和 2059。那麼問題來了:到底在哪個內存地址上存儲哪個字節呢?它可能是在地址 2056 存儲 29, 2057 存儲 205,2058 存儲 101,2059 存儲 7,就像你寫下這個數字的順序一樣,我們稱之爲高位優先。但是,其他的計算機架構可能是在 2056 存儲 7,2057 存儲 101, 2058 存儲 205, 2059 存儲 29,這樣的順序我們稱之爲低位優先。

針對 2 個字節的以及 8 個字節的存儲方式,也是同樣的。最高位字節稱爲 MSB,最低位字節稱爲 LSB。

好吧,那麼我爲什麼要關心字節序的問題?

這個視情況而定了。通常情況下你不需要關心這個問題。無論你在什麼平臺運行 Java 程序,它的字節序都是一樣的,所以你就無需關心字節序的問題。

但是,當你要處理其他語言產生的數據呢?那麼,字節序就是一個大問題了。你必須得保證你按照數據被編碼的順序來進行解碼,反之亦然。如果你足夠幸運,通常在 API 或者協議規範、文件格式說明中找到關於字節序的說明。如果不巧……祝你好運吧!

最重要的是,你需要清晰的瞭解你所使用的字節序是什麼樣的以及你需要處理的數據的字節序是什麼樣的。如果二者不同,你需要進行額外的處理來保證正確性。還有就是,如果你需要處理無符號數值,你需要確保將正確的字節放到對應 integer/short/long 類型的正確位置。

網絡字節序又是什麼?

當設計 IP 協議的時候,高位優先字節序被設計爲網絡字節序。在 IP 報文中德數值類型都是按照網絡字節序存儲的。產生報文的計算機所使用的字節序稱爲“宿主機字節序”,可能和網絡字節序一樣,也可能不一樣。和網絡字節序一樣,Java 中的字節序是高位優先的。

爲什麼沒有無符號類型?

爲什麼 Java 不提供無符號類型呢?好問題!我也常常覺得這個事情非常詭異,尤其是當時已經有很多網絡協議都使用無符號類型了。在 1999 年,我在 Web 上也找了很久(那個時候 google 還沒有這麼棒),因爲我總是覺得這事兒不應該是這樣。直到有一天我採訪 Java 發明者中的一位(是 Gosling 嗎?不太記得了,要是我保存了當時的網頁就好了),這位設計者說了一段話,大意是:“嘿!無符號類型把事情搞複雜了,沒有人真正需要無符號類型,所以我們把它趕出去了”。

這裏有一個頁面,是記錄了一次對 James Gosling 的採訪,看看能否收到一些啓發:

http://www.gotw.ca/publications/c_family_interview.htm

問:程序員經常討論使用“簡單語言”編程的優點和缺點。你怎麼看待這個問題?你覺得 C/C++/Java 算是簡單語言嗎?

Ritchie: 略

Stroustrup:略

Gosling:作爲一個語言設計者,我不太理解所謂的“簡單”結束了是什麼意思,我希望 Java 開發者把這個概念留在他自己腦海裏就好啦。舉例來說,按照那個定義,Java 不算是簡單語言。實際上很多語言都會在極端案例下完蛋,那些極端案例是人們都不會理解的。你去問 C 語言開發人員關於無符號的問題,你很快就會發現沒有幾個 C 語言開發人員真正理解無符號類型到底發生了些什麼,什麼是無符號運算。這些事情讓 C 語言變得複雜。我覺得 Java 語言是非常簡單的。

另外,參考:

http://www.artima.com/weblogs/viewpost.jsp?thread=7555

Oak 往事……

by Heinz Kabutz

2003 年 7 月 15

爲了豐富我對 Java 歷史的瞭解,我開始研究 Sun 的網站,無意間發現了 Oak 0.2 的語言規範書。Oak 是 Java 語言最早使用的名稱,這份文檔算是現存的最古老的關於 Oak 的文檔了。

……

無符號整數(3.1 節)

規範書說:“8 比特,16 比特,32 比特,64 比特的,這 4 種不同寬度的整數類型都是有符號的,除非在前面加上 unsigned 修飾符”。

在側欄中又說:“無符號類型尚未實現;可能永遠也不會實現了。” 好吧,就是這樣了。




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