解決java socket在傳輸漢字時出現截斷導致亂碼的問題
當使用socket進行TCP數據傳輸時,傳輸的字符串會編碼成字節數組,當采用utf8編碼時,數字與字母長度為1個字節,而漢字一般為3個字節。這里參考
字符集之在UTF-8中,一個漢字為什么需要三個字節? - 苦澀的茶 - 博客園 (cnblogs.com)
如果傳輸的字符串是數字,字符和漢字混雜。在數據的接收端,每次調用read方法接收的byte數組的長度是一定的,由於數字,字母和漢字對應的utf8編碼長度不同,可能會導致末尾的漢字被截斷。舉個例子:假定接收端每次調用read方法讀取的byte數組長度為20。而發送短發送的字符串為"Hello World! 你好中國!"
,轉換為byte數組為
[0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x20, 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD, 0xE4, 0xB8, 0xAD, 0xE5, 0x9B, 0xBD, 0x21]
在接收端第一次讀取的byte數組長度為20,對應的byte數組為
[0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x20, 0xE4, 0xBD, 0xA0,0xE5, 0xA5, 0xBD, 0xE4]
轉換為字符變成了"Hello World! 你好�"
,最后一個字符出現了亂碼。這是因為前19個字節(英文空格加標點共13個字節,前兩個漢字占六個字節)轉換成了字符串"Hello World! 你好",最后一個字節對應着字符"中"的三個字節[0xE4, 0xB8, 0xAD]中的第一個字節0xE4。但是這個一個字節(首位為1沒有對應的單字節字符)是無法轉換為可見的字符的。這里就是出現了隔斷。"中"的剩余兩個字節為下一個read讀取得到的字節數組的前兩兩個字節。
為了能夠解決截斷問題,我們需要判斷接收到的字節數組的最后兩個字節是否為被截斷的漢字字符的對應的字節。
為了解決這個問題,首先了解一個漢字字符在UTF-8編碼中的格式。基本上常見的漢字字符在UTF-8編碼中都是三個字節。在UTF-8編碼方案中,對三字節編碼的每個字節都做了規定:
如果是三個字節編碼的話,那么第一個字節的前三位為111,第四位為0,剩余的兩個字節的前兩位都為10
比如漢字"中",對應的三個字節用二進制可以表示為:[1110 0100, 1011 1000, 1010 1101]
滿足UTF-8編碼的要求。
如何根據編碼格式來判斷得到的byte數組的最后一個或者兩個字節是被截斷的呢。
我們將byte數組的最后兩個字節定義為firstByte和secondByte,分別對應着導數第二個字節和倒數第一個字節。
情況1:
如果倒數第一個字節符合1110 xxxx格式,說明這個字節對應着漢字字符的第一個字節,剩余的兩個字節在下個被接收的byte數組中,發生了截斷。我們需要暫存最后一個字節secondByte,與下面一個byte數組的前兩個字節組合在一起解析出漢字。
情況2:
如果倒數第二個字節的二進制符合1110 xxxx格式,那就說明這個字節對應着漢字字符的第一個字節,最后一個字節對應漢字字符的第二個字節,第三個字節為下個被接收的byte數組第一個字節,發生了截斷。這種情況需要將byte數組的最后兩個字符保存起來,與下面一個byte數組的第一個字節結合起來才能解析出對應的漢字。
對於如何判斷一個字節是否符合1110 xxxx格式,這里我們采用掩碼的方式,保留firstByte的前四位,屏蔽后四位(將后四位置零)。
判斷 firstByte & 11100000 == 11100000
是否成立,如果成立對應第一種情況,否則
判斷secondByte & 11100000 == 11100000
是否成立,成立則對應第二種情況