編碼名稱 | 說明 |
ascii | 7位,與ascii7相同 |
iso8859-1 | 8-位,與 8859_1,iso-8859-1,iso_8859-1,latin1...等相同 |
gb2312-80 | 16位,與gb2312,gb2312-1980,euc_cn,euccn,1381,cp1381, 1383, cp1383, iso2022cn,iso2022cn_gb...等相同 |
gbk | 與ms936相同,注意:區分大小寫 |
utf8 | 與utf-8相同 |
gb18030 | 與cp1392、1392相同,目前支持的jdk很少 |
在實際編程時,接觸得比較多的是gb2312(gbk)和iso8859-1。
為什么會有“?”號
上文說過,異種語言之間的轉換是通過unicode來完成的。假設有兩種不同的語言a和b,轉換的步驟為:先把a轉化為unicode,再把unicode轉化為b。
舉例說明。有gb2312中有一個漢字“李”,其編碼為“c0ee”,欲轉化為iso8859-1編碼。步驟為:先把“李”字轉化為unicode,得到“674e”,再把“674e”轉化為iso8859-1字符。當然,這個映射不會成功,因為iso8859-1中根本就沒有與“674e”對應的字符。
當映射不成功時,問題就發生了!當從某語言向unicode轉化時,如果在某語言中沒有該字符,得到的將是unicode的代碼“/uffffd”(“/u”表示是unicode編碼,)。而從unicode向某語言轉化時,如果某語言沒有對應的字符,則得到的是“0x3f”(“?”)。這就是“?”的由來。
例如:把字符流buf =“0x80 0x40 0xb0 0xa1”進行new string(buf, "gb2312")操作,得到的結果是“/ufffd/u554a”,再println出來,得到的結果將是“?啊”,因為“0x80 0x40”是gbk中的字符,在gb2312中沒有。
再如,把字符串string="/u00d6/u00ec/u00e9/u0046/u00bb/u00f9"進行new string (buf.getbytes("gbk"))操作,得到的結果是“3fa8aca8a6463fa8b4”,其中,“/u00d6”在“gbk”中沒有對應的字符,得到“3f”,“/u00ec”對應著“a8ac”,“/u00e9”對應著“a8a6”,“0046”對應著“46”(因為這是ascii字符),“/u00bb”沒找到,得到“3f”,最后,“/u00f9”對應著“a8b4”。把這個字符串println一下,得到的結果是“?ìéf?ù”??吹經]?這里并不全是問號,因為gbk與unicode映射的內容中除了漢字外還有字符,本例就是最好的明證。
所以,在漢字轉碼時,如果發生錯亂,得到的不一定都是問號噢!不過,錯了終究是錯了,50步和100步并沒有質的差別。
或者會問:如果源字符集中有,而unicode中沒有,結果會如何?回答是不知道。因為我手頭沒有能做這個測試的源字符集。但有一點是肯定的,那就是源字符集不夠規范。在java中,如果發生這種情況,是會拋出異常的。
什么是utf
utf,是unicode text format的縮寫,意為unicode文本格式。對于utf,是這樣定義的:
?。?)如果unicode的16位字符的頭9位是0,則用一個字節表示,這個字節的首位是“0”,剩下的7位與原字符中的后7位相同,如“/u0034”(0000 0000 0011 0100),用“34” (0011 0100)表示;(與源unicode字符是相同的);
?。?)如果unicode的16位字符的頭5位是0,則用2個字節表示,首字節是“110”開頭,后面的5位與源字符中除去頭5個零后的最高5位相同;第二個字節以“10”開頭,后面的6位與源字符中的低6位相同。如“/u025d”(0000 0010 0101 1101),轉化后為“c99d”(1100 1001 1001 1101);
?。?)如果不符合上述兩個規則,則用三個字節表示。第一個字節以“1110”開頭,后四位為源字符的高四位;第二個字節以“10”開頭,后六位為源字符中間的六位;第三個字節以“10”開頭,后六位為源字符的低六位;如“/u9da7”(1001 1101 1010 0111),轉化為“e9b6a7”(1110 1001 1011 0110 1010 0111);
可以這么描述java程序中unicode與utf的關系,雖然不絕對:字符串在內存中運行時,表現為unicode代碼,而當要保存到文件或其它介質中去時,用的是utf。這個轉化過程是由writeutf和readutf來完成的。
好了,基礎性的論述差不多了,下面進入正題。
先把這個問題想成是一個黑匣子。先看黑匣子的一級表示:
input(charseta)->process(unicode)->output(charsetb)
簡單,這就是一個ipo模型,即輸入、處理和輸出。同樣的內容要經過“從charseta到unicode再到charsetb”的轉化。
再看二級表示:
sourcefile(jsp,java)->class->output
在這個圖中,可以看出,輸入的是jsp和java源文件,在處理過程中,以class文件為載體,然后輸出。再細化到三級表示:
jsp->temp file->class->browser,os console,db
app,servlet->class->browser,os console,db
這個圖就更明白了。jsp文件先生成中間的java文件,再生成class。而servlet和普通app則直接編譯生成class。然后,從class再輸出到瀏覽器、控制臺或數據庫等。
jsp:從源文件到class的過程
jsp的源文件是以“.jsp”結尾的文本文件。在本節中,將闡述jsp文件的解釋和編譯過程,并跟蹤其中的中文變化。
1、jsp/servlet引擎提供的jsp轉換工具(jspc)搜索jsp文件中用<%@ page contenttype ="text/html; charset=<jsp-charset>"%>中指定的charset。如果在jsp文件中未指定<jsp-charset>,則取jvm中的默認設置file.encoding,一般情況下,這個值是iso8859-1;
2、jspc用相當于“javac –encoding <jsp-charset>”的命令解釋jsp文件中出現的所有字符,包括中文字符和ascii字符,然后把這些字符轉換成unicode字符,再轉化成utf格式,存為java文件。ascii碼字符轉化為unicode字符時只是簡單地在前面加“00”,如“a”,轉化為“/u0041”(不需要理由,unicode的碼表就是這么編的)。然后,經過到utf的轉換,又變回“41”了!這也就是可以使用普通文本編輯器查看由jsp生成的java文件的原因;
3、引擎用相當于“javac –encoding unicode”的命令,把java文件編譯成class文件;
先看一下這些過程中中文字符的轉換情況。有如下源代碼:
<%@ page contenttype="text/html; charset=gb2312"%> <html><body> <% string a="中文"; out.println(a); %> </body></html> |
<%@ page contenttype="text/html; charset=iso-8859-1"%> <html><body> <% string a="中文"; out.println(a); %> </body></html> |
jsp-charset | jsp文件中 | java文件中 | class文件中 |
gb2312 | d6 d0 ce c4(gb2312) | 從/u4e2d/u6587(unicode)到e4 b8 ad e6 96 87 (utf) | e4 b8 ad e6 96 87 (utf) |
iso-8859-1 | d6 d0 ce c4 (gb2312) | 從/u00d6/u00d0/u00ce/u00c4 (unicode)到c3 96 c3 90 c3 8e c3 84 (utf) | c3 96 c3 90 c3 8e c3 84 (utf) |
無(默認=file.encoding) | 同iso-8859-1 | 同iso-8859-1 | 同iso-8859-1 |
servlet:從源文件到class的過程
servlet源文件是以“.java”結尾的文本文件。本節將討論servlet的編譯過程并跟蹤其中的中文變化。
用“javac”編譯servlet源文件。javac可以帶“-encoding <compile-charset>”參數,意思是“用< compile-charset >中指定的編碼來解釋serlvet源文件”。
源文件在編譯時,用<compile-charset>來解釋所有字符,包括中文字符和ascii字符。然后把字符常量轉變成unicode字符,最后,把unicode轉變成utf。
在servlet中,還有一個地方設置輸出流的charset。通常在輸出結果前,調用httpservletresponse的setcontenttype方法來達到與在jsp中設置<jsp-charset>一樣的效果,稱之為<servlet-charset>。
注意,文中一共提到了三個變量:<jsp-charset>、<compile-charset>和<servlet-charset>。其中,jsp文件只與<jsp-charset>有關,而<compile-charset>和<servlet-charset>只與servlet有關。
看下例:
import javax.servlet.*; import javax.servlet.http.*; class testservlet extends httpservlet { public void doget(httpservletrequest req,httpservletresponse resp) throws servletexception,java.io.ioexception { resp.setcontenttype("text/html; charset=gb2312"); java.io.printwriter out=resp.getwriter(); out.println("<html>"); out.println("#中文#"); out.println("</html>"); } } |
compile-charset | servlet源文件中 | class文件中 | 等效的unicode碼 |
gb2312 | d6 d0 ce c4 (gb2312) | e4 b8 ad e6 96 87 (utf) | /u4e2d/u6587 (在unicode中=“中文”) |
iso-8859-1 | d6 d0 ce c4 (gb2312) | c3 96 c3 90 c3 8e c3 84 (utf) | /u00d6 /u00d0 /u00ce /u00c4 (在d6 d0 ce c4前面各加了一個00) |
無(默認) | d6 d0 ce c4 (gb2312) | 同iso-8859-1 | 同iso-8859-1 |
給出如下結論:
在class輸出字符串前,會將unicode的字符串按照某一種內碼重新生成字節流,然后把字節流輸入,相當于進行了一步“string.getbytes(???)”操作。???代表某一種字符集。
如果是servlet,那么,這種內碼就是在httpservletresponse.setcontenttype()方法中指定的內碼,也就是上文定義的<servlet-charset>。
如果是jsp,那么,這種內碼就是在<%@ page contenttype=""%>中指定的內碼,也就是上文定義的<jsp-charset>。
如果是java程序,那么,這種內碼就是file.encoding中指定的內碼,默認為iso8859-1。
當輸出對象是瀏覽器時
以流行的瀏覽器ie為例。ie支持多種內碼。假如ie接收到了一個字節流“d6 d0 ce c4”,你可以嘗試用各種內碼去查看。你會發現用“簡體中文”時能得到正確的結果。因為“d6 d0 ce c4”本來就是簡體中文中“中文”兩個字的編碼。
ok,完整地看一遍。
jsp:源文件為gb2312格式的文本文件,且jsp源文件中有“中文”這兩個漢字
如果指定了<jsp-charset>為gb2312,轉化過程如下表。
表4 jsp-charset = gb2312時的變化過程
序號 | 步驟說明 | 結果 |
1 | 編寫jsp源文件,且存為gb2312格式 | d6 d0 ce c4 (d6d0=中 cec4=文) |
2 | jspc把jsp源文件轉化為臨時java文件,并把字符串按照gb2312映射到unicode,并用utf格式寫入java文件中 | e4 b8 ad e6 96 87 |
3 | 把臨時java文件編譯成class文件 | e4 b8 ad e6 96 87 |
4 | 運行時,先從class文件中用readutf讀出字符串,在內存中的是unicode編碼 | 4e 2d 65 87(在unicode中4e2d=中 6587=文) |
5 | 根據jsp-charset=gb2312把unicode轉化為字節流 | d6 d0 ce c4 |
6 | 把字節流輸出到ie中,并設置ie的編碼為gb2312(作者按:這個信息隱藏在http頭中) | d6 d0 ce c4 |
7 | ie用“簡體中文”查看結果 | “中文”(正確顯示) |
序號 | 步驟說明 | 結果 |
1 | 編寫jsp源文件,且存為gb2312格式 | d6 d0 ce c4 (d6d0=中 cec4=文) |
2 | jspc把jsp源文件轉化為臨時java文件,并把字符串按照iso8859-1映射到unicode,并用utf格式寫入java文件中 | c3 96 c3 90 c3 8e c3 84 |
3 | 把臨時java文件編譯成class文件 | c3 96 c3 90 c3 8e c3 84 |
4 | 運行時,先從class文件中用readutf讀出字符串,在內存中的是unicode編碼 | 00 d6 00 d0 00 ce 00 c4 (啥都不是!?。。?/td> |
5 | 根據jsp-charset=iso8859-1把unicode轉化為字節流 | d6 d0 ce c4 |
6 | 把字節流輸出到ie中,并設置ie的編碼為iso8859-1(作者按:這個信息隱藏在http頭中) | d6 d0 ce c4 |
7 | ie用“西歐字符”查看結果 | 亂碼,其實是四個ascii字符,但由于大于128,所以顯示出來的怪模怪樣 |
8 | 改變ie的頁面編碼為“簡體中文” | “中文”(正確顯示) |
序號 | 步驟說明 | 結果 |
1 | 編寫jsp源文件,且存為gb2312格式 | d6 d0 ce c4 (d6d0=中 cec4=文) |
2 | jspc把jsp源文件轉化為臨時java文件,并把字符串按照iso8859-1映射到unicode,并用utf格式寫入java文件中 | c3 96 c3 90 c3 8e c3 84 |
3 | 把臨時java文件編譯成class文件 | c3 96 c3 90 c3 8e c3 84 |
4 | 運行時,先從class文件中用readutf讀出字符串,在內存中的是unicode編碼 | 00 d6 00 d0 00 ce 00 c4 |
5 | 根據jsp-charset=iso8859-1把unicode轉化為字節流 | d6 d0 ce c4 |
6 | 把字節流輸出到ie中 | d6 d0 ce c4 |
7 | ie用發出請求時的頁面的編碼查看結果 | 視情況而定。如果是簡體中文,則能正確顯示,否則,需執行表5中的第8步 |
序號 | 步驟說明 | 結果 |
1 | 編寫servlet源文件,且存為gb2312格式 | d6 d0 ce c4 (d6d0=中 cec4=文) |
2 | 用javac –encoding gb2312把java源文件編譯成class文件 | e4 b8 ad e6 96 87 (utf) |
3 | 運行時,先從class文件中用readutf讀出字符串,在內存中的是unicode編碼 | 4e 2d 65 87 (unicode) |
4 | 根據servlet-charset=gb2312把unicode轉化為字節流 | d6 d0 ce c4 (gb2312) |
5 | 把字節流輸出到ie中并設置ie的編碼屬性為servlet-charset=gb2312 | d6 d0 ce c4 (gb2312) |
6 | ie用“簡體中文”查看結果 | “中文”(正確顯示) |
序號 | 步驟說明 | 結果 |
1 | 編寫servlet源文件,且存為gb2312格式 | d6 d0 ce c4 (d6d0=中 cec4=文) |
2 | 用javac –encoding iso8859-1把java源文件編譯成class文件 | c3 96 c3 90 c3 8e c3 84?。╱tf) |
3 | 運行時,先從class文件中用readutf讀出字符串,在內存中的是unicode編碼 | 00 d6 00 d0 00 ce 00 c4 |
4 | 根據servlet-charset=iso8859-1把unicode轉化為字節流 | d6 d0 ce c4 |
5 | 把字節流輸出到ie中并設置ie的編碼屬性為servlet-charset=iso8859-1 | d6 d0 ce c4 (gb2312) |
6 | ie用“西歐字符”查看結果 | 亂碼(原因同表5) |
7 | 改變ie的頁面編碼為“簡體中文” | “中文”(正確顯示) |
序號 | 步驟說明 | 結果 | 域 |
1 | 在ie中輸入“中文” | d6 d0 ce c4 | ie |
2 | ie把字符串轉變成utf,并送入傳輸流中 | e4 b8 ad e6 96 87 | |
3 | servlet接收到輸入流,用readutf讀取 | 4e 2d 65 87(unicode) | servlet |
4 | 編程者在servlet中必須把字符串根據gb2312還原為字節流 | d6 d0 ce c4 | |
5 | 編程者根據數據庫內碼iso8859-1生成新的字符串 | 00 d6 00 d0 00 ce 00 c4 | |
6 | 把新生成的字符串提交給jdbc | 00 d6 00 d0 00 ce 00 c4 | |
7 | jdbc檢測到數據庫內碼為iso8859-1 | 00 d6 00 d0 00 ce 00 c4 | jdbc |
8 | jdbc把接收到的字符串按照iso8859-1生成字節流 | d6 d0 ce c4 | |
9 | jdbc把字節流寫入數據庫中 | d6 d0 ce c4 | |
10 | 完成數據存儲工作 | d6 d0 ce c4 數據庫 | |
以下是從數據庫中取出數的過程 | |||
11 | jdbc從數據庫中取出字節流 | d6 d0 ce c4 | jdbc |
12 | jdbc按照數據庫的字符集iso8859-1生成字符串,并提交給servlet | 00 d6 00 d0 00 ce 00 c4 (unicode) | |
13 | servlet獲得字符串 | 00 d6 00 d0 00 ce 00 c4 (unicode) | servlet |
14 | 編程者必須根據數據庫的內碼iso8859-1還原成原始字節流 | d6 d0 ce c4 | |
15 | 編程者必須根據客戶端字符集gb2312生成新的字符串 | 4e 2d 65 87 (unicode) | |
servlet準備把字符串輸出到客戶端 | |||
16 | servlet根據<servlet-charset>生成字節流 | d6d0 ce c4 | servlet |
17 | servlet把字節流輸出到ie中,如果已指定<servlet-charset>,還會設置ie的編碼為<servlet-charset> | d6 d0 ce c4 | |
18 | ie根據指定的編碼或默認編碼查看結果 | “中文”(正確顯示) | ie |
最大的網站源碼資源下載站,
新聞熱點
疑難解答