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