[概述]
????? 在編程中字符編碼絕對是個值得重視的問題,當讀取一個文件或是得到一個輸入流,你需要分析數據的編碼方式、形態,以便能正確的處理、顯示數據所表示的字符。
[細節]
????? 1)? 在簡體中文操作系統中,從鍵盤輸入的原始字符采用的是GBK編碼方式,對應到其他操作系統,采用的應是系統默認的本地字符集。而在程序設計語言中,字符和字符串則通常是使用Unicode編碼方式,這一點可以用下列代碼說明(使用Java語言描述)。
????? int ch = System.in.read(); //從鍵盤輸入中讀取一個字節的數據
????? 如果輸入“中國”兩個字符,使用上面的代碼將所有的數據讀取,將得到“D6 D0 B9 FA”,這寫數據正是“中國”兩個字符的GBK編碼值。
????? String tmp = "中國"; //定義字符串并賦值
????? for(int i=0;i<tmp.length();i++)
????? {
????? //將字符串中的每個字符編碼值以十六進制形式顯示
???????? System.out.println(Integer.toHexString(ch));
????? }
????? 上面代碼最后顯示的內容是“4E2D 56FD”,而這正是“中國”兩個字符的Unicode編碼值。
?
????? 2) 不僅是輸入的原始字符采用GBK編碼,屏幕輸出的最終數據也要采用GBK編碼,下面的代碼能說明問題。
????? String tmp = "中國"; //定義字符串并賦值
????? System.out.println(tmp); //將字符串tmp輸出到顯示屏
????? 代碼運行后,在顯示屏上顯然能看到“中國”兩個字符,而字符串tmp分明是采用Unicode進行編碼的,是不是剛才提出的命題站不住腳呢?其實,在調用println()方法后,該方法自動的將字符串tmp的編碼方式從Unicode轉換成了本地編碼GBK,這樣才能在屏幕上正常的顯示中文。如果你仍然懷疑,請繼續往下看。
????? byte [] buf1 = tmp.getBytes("Unicode"); //將字符串tmp以Unicode編碼方式儲存在字節數組中
????? byte [] buf2 = tmp.getBytes("GB2312"); //將字符串tmp以UGBK編碼方式儲存在字節數組中
????? //在屏幕輸出流中直接寫字節數組
????? System.out.write(buf1);
????? System.out.write(buf2);
????? 這樣的作法將會得到什么結果呢?結果也許會令你感到驚訝,buf1的數據輸出后顯示為亂碼,而buf2的數據輸出后赫然顯示為“中國”兩個字符。可以把命題說得明確點:如果要在屏幕上輸出漢字,那么字符的最終編碼方式必須是GBK編碼方式。對于數字和英文字母,以及ASCII編碼集中包含的符號,字符的最終編碼方式可以是ASCII,這種情況下如果使用Unicode編碼,那么顯示的結果會是這樣“1 2 3 a b c ”,本來想顯示的內容是“123abc”。顯示結果字符間多了個貌似空格的字符,這是因為ASCII編碼使用一個字節,Unicode編碼使用兩個字節,在ASCII編碼轉換為Unicode時候,只是單純地在編碼值前面補充一個全為0的字節,這個字節在最終顯示的時候被看做是空字符NUL。
?
????? 3) 在涉及網頁、網絡流和關系數據庫方面編程的時候,字符編碼總喜歡戲弄編程人員,不花心思去馴服它的結果將是得到一堆亂碼。例如在讀取數據數據時候,數據庫中的內容是中文字符,如果數據庫沒有考慮到中文支持問題就很容易得到亂碼。再例如程序運行的平臺默認編碼并非GBK,在獲取GBK編碼的字符數據時候,程序會將數據看作默認編碼,這樣也容易產生亂碼。在上述情況中編寫程序的時候,就應該耐心的分析數據的編碼方式,合理的編寫代碼防止亂碼。
?
[例子]???
????? 記得在《Java手機程序設計入門與應用》(王森 編著)一書的第13章-MIDP網絡程序設計中有一段使用HTTP進行網絡連接的實例代碼,部分代碼如下所示。
????? 記得在《Java手機程序設計入門與應用》(王森 編著)一書的第13章-MIDP網絡程序設計中有一段使用HTTP進行網絡連接的實例代碼,部分代碼如下所示。
????? String url = " http://127.0.0.1/test.html ";
????? HttpConnection hc = (HttpConnection)Connector.open(url);
????? DataInputStream dis = new DataInputStream(hc.openInputStream());
????? String content = "";
????? int ic;
????? while((ic = dis.read()) != -1)
????? {
????????? content += (char)ic;
????? }
????? Form f = new Form("HTTP Test");
????? f.append(content);
????? Display.getDisplay(this).setCurrent(f);
????? 這段代碼讓手機通過HTTP協議與網絡中的主機進行通信,然后獲得網絡主機上的文件test.html并將文件內容讀取到字符串變量中,最后顯示到程序窗體中。如果程序這般執行的話,你會發現MIDlet顯示出來中文都是亂碼。作者稱“之所以會有這種結果,原因在于我們的仿真器支持Unicode的緣故。”,作者的意思似乎是MIDlet將本地編碼的字符數據誤認為了Unicode編碼的數據,因此不能正常顯示,然后推薦了一種解決方法:使用ASCII形態的Unicode。
????? 所謂ASCII形態的Unicode指的是使用ASCII編碼的字符來表示Unicode編碼值,反過來說就是將Unicode的編碼值看做字符,再用ASCII對這些字符進行編碼存放。比如“中國”這兩個字符的ASCII形態的Unicode編碼字符為“\u4e2d\u56fd”,0x4E2D 0x56FD 分別為“中”和“國”的Unicode編碼值,將編碼值作為字符,然后在前面添加“\u”標識符,以便進行還原。再對這些字符進行ASCII編碼就得到了ASCII形態的Unicode編碼值,最終的值為“5C 75 34 65 32 64 5C 75 35 36 66 64”,一共12個字節的數據,分別對應了“\u4e2d\u56fd”中的一個字符。使用jdk*\bin文件夾下的native2ascii.exe程序可以很方便的將一個文件轉換為ASCII形態的Unicode編碼。將文件test.html轉換形態后,MIDlet中需要再次將ASCII形態的Unicode轉換為Unicode編碼,這個轉換方法需要自己寫,最后MIDlet中顯示出來的就是正常的中文字符。
????? 在我看來,那本書的作者沒有把握住問題的真正原因,也或許是我們使用的模擬器和平臺不同。如果把握住真正的原因,問題的解決方法就變得很簡單了。前面說過,要在屏幕上顯示出中文,字符的最終編碼形式必須是GBK,在中國大陸發現的手機都能顯示中文,也就說明手機中都支持GBK編碼。那為什么會出現中文字符亂碼的問題呢?我的理由是手機中采用的默認編碼是ISO8859-1,對于從網絡中讀入的字符數據,在沒有指明的情況下,MIDlet一律將它們看作是ISO8859-1編碼的數據。而test.html的編碼方式是GBK,MIDlet犯了個錯誤,它將GBK編碼的數據誤認為了ISO8859-1編碼的數據,然后在顯示的時候又進行了一次ISO8859-1到GBK的編碼轉換,這樣的結果是數據遭到了破壞,顯示出來的中文也就變成了亂碼。
????? 把握住了原因,解決起來就十分方便了。既然MIDlet將GBK編碼的數據誤認為ISO8859-1編碼的數據,那么我們只要在程序中指明數據的編碼方式就可以了,而不用使用“ASCII形態的Unicode”這樣的舍本求末的方法。下面是解決MIDlet網絡連接中文亂碼問題的代碼,這些代碼將證明我的觀點。
????? http://127.0.0.1/test.html;
????? HttpConnection hc = (HttpConnection)Connector.open(url);
??????byte [] buf = new byte[1024];
????? int len = hc.openInputStream().read(buf);? //讀取網絡數據
????? String content = new String(buf,0,len,"GB2312");? //指定數據為GBK編碼
??????Form f = new Form("HTTP Test");
????? f.append(content);?
????? display.setCurrent(f);
????? 上述代碼中關鍵的一句是:
????? String content = new String(buf,0,len,"GB2312");
????? 這句代碼告訴MIDlet從網絡中讀取的數據使用的是GBK編碼方式,然后MIDlet便能爭取處理和顯示這些數據。
????? 如果將這句代碼改寫為:?
????? String content = new String(buf,0,len);
????? 或者是:
??????String content = new String(buf,0,len,"ISO8859-1");
????? 都將出現同樣的中文亂碼現象,由此斷定錯誤的原因是手機默認編碼使用ISO8859-1,MIDlet將從網絡中讀取的GBK編碼的數據誤認成了ISO8859-1編碼的數據。