2012年7月5日 星期四

JDBC與字元集總結


通過JDBC訪問資料庫時遇到的字元集問題中,可以歸納為如下因素:
- JVM對字元集的處理
JVM核心完全使用Unicode字元集,編碼上採用UTF-16LE(x86和Unix)。 Java編譯器掃描.java原始檔案時將完成預轉換,比如在中文Windows上編譯.java檔時,你可能已經注意到.java檔中的字串和.class中的不一樣。因為.java檔本身用的是gb2312編碼,而.class內則是UTF-16LE編碼。如果你的編輯器支持,你可能會選擇直接用UTF-8來書寫.java來源程式,這時Java編譯器就會用UTF-8對來源程式解碼。
在輸出時,比如調用System.out.print方法也將完成一個編碼轉換,在上述情況中經常是將記憶體中的UTF-16LE編碼的字串轉換成控制台上可讀的gb2312編碼。

- JSP頁面使用的字元集
運行JSP頁面前總會被預處理至.java程式並被編譯成.class,注意到JSP總是一個servlet,因此實際上這裡存在兩個字元集,一是.jsp檔本身使用的字元集,另一則是servlet輸出內容的字元集(content-type)。儘量使.jsp檔本身的字元集和輸出內容的字元集保持一致,比如一致採用UTF-8。Response的實現將jvm中的UTF-16LE字串轉換至<%@page encoding=...%>所指定的編碼,

- Connection 使用的字元集
連接的字元集限制了SQL語句可以使用的字元。這在UTF-16中格外明顯,如果連接不使用UTF-16的字元集,那麼由於大多數的Latin-1字元集對'\0'的處理將使大多數SQL語句成為無效語句,比如SELECT語句通過UTF-16LE編碼後將變成"S\0E\0L\0E\0C\0T\0...",伺服器的SQL分析器在遇到第一個'\0'便認為語句已經結束。

但仍然可以將UTF-16LE編碼的字串送入Latin-1字元集的連接,方法是SQL語句本身仍採用Latin-1編碼,而相關的字串(引號內部的)採用UTF-16LE。這種情況下,UTF-16LE的字串不能包括Unicode字元集中編碼小於256的字元(包括拉丁字母和數位、英文符號),否則SQL分析器會報告"字串未結束"之類的錯誤。(為什麼?)
- 資料庫系統
並不是所有資料庫都支援Unicode,你可能有必要通過字元集轉換來保存一些特殊的字元資料。如果資料庫僅支援Latin-1字元集(這樣的系統不在少數),對於中文的情況,你可以將字串用Latin-1編碼,然後用gb2312解碼,覺得困惑?如果你(曾經)是C++程式師,那麼這裡的編碼類似於dynamic_cast, 而解碼則相當於reinterpret_cast。
sql_str = new String( java_str.getBytes("ISO-8859-1"), "gb2312" );
在獲取資料的時候則剛好相反:
java_str = new String( sql_str.getBytes("gb2312"), "ISO-8859-1" );

如果資料庫系統支援Unicode,那麼請儘量採用Unicode。有些手冊上建議你根據具體情況決定是否使用Unicode,因為Unicode將佔用更多的存儲空間,而且如果採用UTF-8,排序的速度將會"減慢30% (mysql)",請不要為這些詞語而顧慮,大多數情況這些都不是問題。
對於SQL Server 2000,這篇文章值得一讀:
http://www.microsoft.com/china/msdn/library/techart/IntlFeaturesInSQLServer2000.asp
最關鍵的就是你需要在字串左邊加上N字元(N一定要大寫),如
INSERT INTO table(name_en, name_native) VALUES('yokohama', N' 橫? ')

對於Sybase資料庫(Sybase 11.5, Sybase 12),系統不支援UTF-16,但支援UTF-8,為了使用Unicode,你可能需要下面的連接字串:
jdbc:sybase:Tds:127.0.0.1:4000/database?charset=utf8&jconnect_version=0
類似的,在SQL語句中使用字元N修飾的字串,使SQL分析器認為字串是Unicode編碼的。
對於MySQL資料庫,系統支援四個級別的字元集設置:
連接,資料庫,表,欄位
MySQL參考手冊第9章有詳細的討論,但注意版本要求4.1.0以上,同時Windows (nt,2k,xp) 的用戶請注意 4.1.0 有個bug,你必須使用4.1.1才能正確使用Unicode。
在 SQL Server 和 Sybase 中都有N開頭的欄位類型,它們被設計用於國際化的字元存儲。在SQL Server中,比如NTEXT實際上就是用Unicode存儲的的欄位類型。
SQL-99規定了Unicode字串統一使用 u 首碼,如 u"??????",但目前還沒有見哪個資料庫系統支援這種語法。

附:幾個字元集支援的測試例子 (需要測試用的源碼可以向我要:jljljjl@yahoo.com)
聲明:
Connection c;
Statement s;
生成資料:
String lit1 = "的文本:中華人民共和國]";
String[] encs = new String[] {
"(default)",
"ISO-8859-1",
"cp850",
"gb2312",
"gbk",
"big5",
"UTF-16LE",
"UTF-16BE",
"UTF-8",
};

String javaSrc = "[這是預設編碼" + lit1;
byte[] rawdata;

s.executeUpdate("DELETE FROM StringTable");
for (int i = 0; i < encs.length; i++) {
String targetEncoding = encs[i];
javaSrc = "[這是" + targetEncoding + lit1;
String testTarget;

if (i == 0) {
rawdata = javaSrc.getBytes();
testTarget = new String(rawdata);
} else {
rawdata = javaSrc.getBytes(targetEncoding);
testTarget = new String(rawdata);
}

System.out.println(testTarget);

String sql = ("INSERT INTO StringTable(charset,text) VALUES(" +
"'" + targetEncoding + "', N'" + testTarget + "')");
System.out.println(sql);

s.executeUpdate(sql);
}
獲取資料:
ResultSet rs = s.executeQuery("SELECT * FROM StringTable");

String charset;
String text;
while (rs.next()) {
charset = rs.getString("charset").trim();
text = rs.getString("text");

System.out.println(charset + ": [" + text + "]");

byte[] raws = text.getBytes();

String restore;
if ("(default)".equals(charset)) {
restore = new String(raws);
} else {
restore = new String(raws, charset);
}
System.out.println(" --> [" + restore + "]");
}
典型測試結果:
SQL-Server, type = ntext
(default): [[這是(default)的文本:中華人民共和國]]
--> [[這是(default)的文本:中華人民共和國]]
ISO-8859-1: [[??ISO-8859-1???????????]]
--> [[??ISO-8859-1???????????]]
cp850: [[??cp850???????????]]
--> [[??cp850???????????]]
gb2312: [[這是gb2312的文本:中華人民共和國]]
--> [[這是gb2312的文本:中華人民共和國]]
gbk: [[這是gbk的文本:中華人民共和國]]
--> [[這是gbk的文本:中華人民共和國]]
big5: [[?琌big5?????㎝?]]
--> [[?是big5的文本:中?人民共和?]]
UTF-8: [[榪欐槸UTF-8鐨?枃鏈細涓??烘皯鍏卞拰鍥絔]
--> [[這是UTF-8的文本:中華人民共和國]]

SQL-Server, type = text
(default): [[這是(default)的文本:中華人民共和國]]
--> [[這是(default)的文本:中華人民共和國]]
ISO-8859-1: [[??ISO-8859-1???????????]]
--> [[??ISO-8859-1???????????]]
cp850: [[??cp850???????????]]
--> [[??cp850???????????]]
gb2312: [[這是gb2312的文本:中華人民共和國]]
--> [[這是gb2312的文本:中華人民共和國]]
gbk: [[這是gbk的文本:中華人民共和國]]
--> [[這是gbk的文本:中華人民共和國]]
big5: [[?琌big5?????㎝?]]
--> [[?是big5的文本:中?三民囝和?]]
UTF-8: [[榪欐槸UTF-8鐨?枃鏈細涓??烘皯鍏卞拰鍥絔]
--> [[這是UTF-8的文本:中華人民共和國]]
Sybase, type = char
(default): [[??(default)???????????]]
--> [[??(default)???????????]]
ISO-8859-1: [[??ISO-8859-1???????????]]
--> [[??ISO-8859-1???????????]]
cp850: [[??cp850???????????]]
--> [[??cp850???????????]]
gb2312: [[??gb2312???????????]]
--> [[??gb2312???????????]]
gbk: [[??gbk???????????]]
--> [[??gbk???????????]]
big5: [[??big5???????????]]
--> [[??big5???????????]]
UTF-16LE
--> [[?啦???????????乎?民共?]]
UTF-16BE:
--> [[??????????????乎?共???]
UTF-8: [[???UTF-8?????????????????]
--> [[???UTF-8?????????????????]

Sybase, type = nchar
(default): [[??(default)???????????]]
--> [[??(default)???????????]]
ISO-8859-1: [[??ISO-8859-1???????????]]
--> [[??ISO-8859-1???????????]]
cp850: [[??cp850???????????]]
--> [[??cp850???????????]]
gb2312: [[??gb2312???????????]]
--> [[??gb2312???????????]]
gbk: [[??gbk???????????]]
--> [[??gbk???????????]]
big5: [[??big5???????????]]
--> [[??big5???????????]]
UTF-16LE
--> [[?啦???????????乎?民共?]]
UTF-16BE:
--> [[??????????????乎?共???]
UTF-8: [[???UTF-8?????????????????]
--> [[???UTF-8?????????????????]
Sybase, type = char, charset=utf8
(default): [[這是(default)的文本:中華人民共和國]]
--> [[這是(default)的文本

沒有留言: