開發一個查詢功能時,遇到了一個ORM的問題:數據庫字段是 Blob 類型,裏面實際存儲的是文本數據,Java 後端代碼中用字符串 String 類型去接收這個字段的數據時,報錯,提示沒有對應的setter方法,類型不匹配;換成 byte[] 字節數組類型去接收這個字段的數據,依然報錯,一樣是找不到setter方法,類型不匹配;最後只好將Java中對應的變量類型改成 java.sql.Blob 類型去接收對應的數據,不報錯了,但如何取獲取其中的文本數據呢?java
使用的代碼以下:sql
private String getTextFromBlob(Blob blob) { int i = 1; byte btArr[] = new byte[0]; try { while (i < blob.length()) { byte[] bytes = blob.getBytes(i, 1024); btArr = ArrayUtils.addAll(btArr, bytes); i += 1024; } return new String(btArr, "GB2312"); } catch (Exception e) { logger.error(e.getMessage(), e); return null; } }
代碼邏輯並不複雜,但其實這地方有一個坑,須要注意一下。數據庫
最開始的時候寫的代碼並非這樣,我在 while 循環裏每次拿 1024 個字節的數據,而後使用 new String(bytes,"GB2312") 獲得字符串,再用 StringBuilder 把每次循環獲得的字符串拼接起來,最後 stringBuilder.toString() 返回完整的字符串內容。apache
寫完以後,測試,沒問題。但緊接着,我想到了一個問題:我每次拿1024個字節,會不會正好把組成一個漢字的兩個字節拆分開呢?後端
答案是確定的。雖然 GB2312 編碼字符集固定使用 2 個字節來存儲漢字,可是 GB2312字符集在存儲 ASCII 字符的時候,用的是 1 個字節來存儲。也就是說,對於英文字母、數字、英文標點,GB2312 用一個字節存儲;對於中文,則使用兩個字節存儲。這樣的話就無法保證每次拿1024個字節不會把某個漢字的兩個字節拆分紅兩段。數組
由於數據庫中的文字都比較短,沒有超過一百個字的,程序每次拿1024個字節就把全部的內容都拿完了,因此在測試中返回的文本都是正常的。改了一下代碼,把 1024 改爲 10,每次拿10個字節,果真出現了亂碼問題,文本中的部分中文出現了亂碼,而其餘部分的中文是正常的。性能
最後改爲了上面的代碼,每次依然拿固定長度的字節,而後把結果都放到一個 byte[] 字節數組裏,等拿完全部的字節以後,使用 new String(bytes,"GB2312") 獲得字符串,這樣就避免了上面的問題。實際測試以後(每次拿10個字節),返回的文本正常,沒有亂碼。測試
固然這個代碼並不完美,代碼裏使用 apache collections 包裏的 ArrayUtils.addAll(byte[] b,byte[] c) 方法來把兩個字節數據拼到一塊,其內部的實現方法就是建立一個大數組,而後把兩個數據的內容依次放進去,這樣的話每次都要開闢一個新的內存空間,效率並不高,若是數據量大的話,會有很大的性能開銷。ui
一個比較好的解決方案就是:本身定義一個大數組,每次循環把取到的內容放到這個大數組對應的位置上,避免每次都要 new 一個數組出來,性能更好。缺點就是代碼邏輯會複雜一些。編碼
總結:
在對字節流進行讀取、拆分的時候,須要注意會不會把表示一個字符的幾個字節給誤拆分了,這樣最後獲得的內容會有部分亂碼。像常見的GB23十二、UTF-八、UTF-16等都是變長的方式進行字節存儲,不能進行拆分;而像 UCS-2 這樣的字符集,固定使用兩個字節存儲,按偶數進行拆分就沒問題。