通過前面中文化、國際化問題解決的系列1-4,相信大家對字符集、字符編碼、字符解碼、字符亂碼、Java中文問題解決等都有了一個(gè)比較清晰的認(rèn)識;但文中的信息并非包羅萬象,結(jié)合到自己平時(shí)對于字符集、編碼相關(guān)的一些疑惑,本篇對一些前文中并未提及的一些問題進(jìn)行補(bǔ)充,以便讓該系列更加完善和全面;本文主要解決以下兩個(gè)問題:其一,解決UltraEdit菜單中的 文件 -> 轉(zhuǎn)換 子菜單中涉及的一些名詞疑惑,主要涉及EBCDIC 、OEM字符集、ANSI字符集、HZ編碼等;其二,補(bǔ)充關(guān)于URL編碼相關(guān)的一些知識點(diǎn),涉及瀏覽器、Web服務(wù)器設(shè)置、Servlet規(guī)范等;
更多的與字符集相關(guān)的專業(yè)詞匯簡介
相信很多同學(xué)都有在用UltraEdit這個(gè)文本編輯器軟件,從菜單 文件 --> 轉(zhuǎn)換 進(jìn)入即可看見很多的字符集間的轉(zhuǎn)換子菜單,不同版本間可能會有些差異,可以參考下圖:

從上圖中我們可以看到很多前面已經(jīng)提及的一些詞匯,比如ASCII、Unicode [Big Endian/Little Endian]、UTF-8等;但還有一些未提及的,下面就逐個(gè)介紹下:
- l DOS/UNIX/MAC間的轉(zhuǎn)換:
這幾項(xiàng)和字符集無關(guān),主要是由于各個(gè)系統(tǒng)對行結(jié)束字符的要求不一樣,體現(xiàn)在:Mac格式只要求回車、Unix只要求換行、DOS則要求回車、換行 二者組合;這幾個(gè)命令要做的就是對這幾個(gè)特殊字符進(jìn)行相應(yīng)替換;
- l EBCDIC (Extended Binary Coded Decimal Interchange Code):
他是與ASCII同時(shí)期的一種字符編碼,主要用于IBM計(jì)算機(jī)中;它采用8位碼, 有256個(gè)編碼狀態(tài), 但只選用其中一部分。0-9十個(gè)數(shù)字符的高4位編碼為1111, 低4位仍為0000-1001。大、小寫英文字母的編碼同樣滿足正常的排序要求, 而且有簡單的對應(yīng)關(guān)系, 即同一個(gè)字母的大小寫的編碼值僅最高的第二位的值不同, 易于識別與變換。
- l OEM字符集:
由于ASCII碼只利用了低7位,第8位沒有被使用,而隨著計(jì)算機(jī)的發(fā)展,很多原始設(shè)備制造商(當(dāng)時(shí)最有名的應(yīng)該就是IBM了)覺得ASCII碼已經(jīng)無法滿足需求了,所以他們各自對第8位進(jìn)行了自定義擴(kuò)展,從而形成了五花八門的OEM字符集;由于這些擴(kuò)展都是不通用的,只有各自能夠解釋,這些不兼容的擴(kuò)展又叫做代碼頁(Code Page),在同一臺機(jī)器中,通過不同的代碼頁就可以實(shí)現(xiàn)系統(tǒng)間信息的轉(zhuǎn)換了;
- l ANSI字符集:
正如前面提到的,眾多的OEM廠商對第8位進(jìn)行了自定義擴(kuò)展;但更加夸張和恐怖的事情是,隨著計(jì)算機(jī)進(jìn)入亞洲,此時(shí)卻發(fā)現(xiàn)整整用完8位也無法將所有的他們需要的語言字符;于是在ASCII的基礎(chǔ)上他們再次進(jìn)行了擴(kuò)展,從而產(chǎn)生了雙字節(jié)字符集DBCS(double byte character set),我們常見的國標(biāo)系列GB2312/GBK/BIG5......都屬于此類;這些字符集有一個(gè)共同的特點(diǎn):完全兼容ASCII!這是ANSI系列和Unicode系列最大的不同點(diǎn);這類型基于ASCII碼進(jìn)行擴(kuò)展的多字節(jié)字符集MBCS統(tǒng)稱為ANSI字符集;注意:ANSI字符集之間也是無法直接識別的,需要進(jìn)行自定義轉(zhuǎn)換;
- l 提到國標(biāo)系列,在此我還想提一個(gè)編碼:HZ碼,HZ是漢字的簡稱?
他是基于GB字符集的一種簡體中文編碼,在新加坡被廣泛使用;編碼規(guī)則為:他將每個(gè)字節(jié)中的第8位(在GB字符集中該位為1)屏蔽,只保留低7位,并將經(jīng)過變換后的部分用~{、~}括起來;解碼的時(shí)候只需要將括號對里面的部分高位還原成1即可;這樣就可以在互聯(lián)網(wǎng)上廣泛的傳輸了,而不必受制于某些系統(tǒng)只能接收基于ASCII碼的字符;
URL編碼
基于Java開發(fā)的Web應(yīng)用URL組成如下:
http://domain:port/contextPath/servletPath/pathInfo?queryString
其中各個(gè)部分含義如下:
Domain、Port:分別是域名和端口;
contextPath:應(yīng)用上下文路徑,默認(rèn)為應(yīng)用名稱,比如我們的apps;但可以通過應(yīng)用服務(wù)器的相關(guān)配置進(jìn)行修改,一般線上環(huán)境會修改成/,此時(shí)相當(dāng)于contextPath為空;
servletPath:Servlet路徑,一般在應(yīng)用的web.xml文件中配置servlet-mapping;但由于現(xiàn)在的web應(yīng)用一般都會用一些框架,比如Struts、Webwork等,此時(shí)各框架都會對此進(jìn)行封裝,會在另外的配置文件中進(jìn)行設(shè)置;但原理都是一樣的;
pathInfo:可以理解為最終接收用戶請求的具體執(zhí)行類,比如我們常說的Action;
queryString:get方式傳入的請求參數(shù);
以上各個(gè)部分中可能存在中文問題的是pathInfo、queryString兩個(gè)部分;
首先,我們來看下Servlet中和URL相關(guān)的一些api及其注意事項(xiàng):
HttpServletRequest.setCharacterEncoding(); //僅僅只適用于設(shè)置post提交的request body的編碼而不是設(shè)置get方法提交的queryString的編碼。該方法還告訴應(yīng)用服務(wù)器應(yīng)該采用什么編碼解析post傳過來的內(nèi)容;注意:若沒有設(shè)定characterEncoding,則使用ISO-8859-1來解碼用戶輸入的表單,而不是使用系統(tǒng)默認(rèn)的編碼。
HttpServletResponse.setContentType(); //告訴瀏覽器網(wǎng)頁中數(shù)據(jù)是什么編碼;表單提交時(shí),根據(jù)ContentType指定的charset對表單中的數(shù)據(jù)編碼,然后發(fā)送給服務(wù)器。
HttpServletRequest.getParameter("name"); //返回的字符串為:queryString(包括get和post),其值經(jīng)過Servlet服務(wù)器URL Decode過的,默認(rèn)編碼來源于應(yīng)用服務(wù)器中的配置,比如tomcat中server.xml的URIEncoding。
HttpServletRequest.getPathInfo(); //返回的字符串為:pathinfo;由Servlet服務(wù)器解碼(decode)過的。默認(rèn)編碼同上,tomcat中可設(shè)置useBodyEncodingForURI。
HttpServletRequest.getRequestURI(); //返回的字符串為:contextPath/servletPath/pathinfo;注意是瀏覽器提交過來的原始數(shù)據(jù),未被Servlet服務(wù)器URL Decode過。
對URL編碼【URL Encoding/Percent Encoding】時(shí),使用以下規(guī)則:
字母數(shù)字字符 "a" 到 "z"、"A" 到 "Z" 和 "0" 到 "9" 保持不變。
特殊字符 "."、"-"、"*" 和 "_" 保持不變。
空格字符 " " 轉(zhuǎn)換為一個(gè)加號 "+"。
所有其他字符都是不安全的,因此首先使用一些編碼機(jī)制將它們轉(zhuǎn)換為一個(gè)或多個(gè)字節(jié)。然后每個(gè)字節(jié)用一個(gè)包含 3 個(gè)字符的字符串 "%xy" 表示,其中 xy 為該字節(jié)的兩位十六進(jìn)制表示形式。推薦的編碼機(jī)制是 UTF-8。但是,出于兼容性考慮,如果未指定一種編碼,則使用相應(yīng)平臺的默認(rèn)編碼。
假定我們待請求URL為:http://localhost:8080/example/中國?name=中國;
Html內(nèi)content-type或meta中的charset=GBK;文件格式為ANSI/ASCII;
URL中的兩個(gè)漢字"中國"的各字符集下的編碼為:
漢字 |
編碼 |
二進(jìn)制表示 |
中國 |
UTF-8 |
0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67] |
中國 |
GBK |
0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6] |
中國 |
ISO8859-1 |
0x3f 0x3f[63, 63][??] |
對于Get方式的URL請求有兩種情況,其一:用戶直接在瀏覽器地址欄中輸入U(xiǎn)RL,此時(shí)瀏覽器沒有編碼可參考,直接用瀏覽器的默認(rèn)編碼進(jìn)行解析并提交到服務(wù)端;其二:在form表單內(nèi)提交,只是form屬性method為GET,此時(shí)瀏覽器會參考目前html中對編碼的相關(guān)設(shè)置進(jìn)行解析,比如content-type或meta中的charset。
以下就重點(diǎn)講講第二種方式的提交:
GET方式form submit:瀏覽器會對URL進(jìn)行URL encoding,然后發(fā)送給服務(wù)器。
- 對于中文IE,如果在高級選項(xiàng)中選中總以UTF-8發(fā)送(默認(rèn)方式),則PathInfo在URL Encoding時(shí)按照UTF-8編碼;QueryString按照GBK編碼。
此時(shí)提交是:GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
- 對于中文IE,如果在高級選項(xiàng)中取消總以UTF-8發(fā)送,則PathInfo和QueryString在URL encoding時(shí)按照GBK編碼。
此時(shí)提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
- 對于中文firefox、Chrome,因?yàn)闆]有類似IE中的這種設(shè)置選項(xiàng),所以這兩種瀏覽器中對pathInfo的編碼規(guī)則沒有做特殊處理,MS是和queryString采取同樣的編碼規(guī)則;在本例中的假設(shè)情況下:pathInfo和queryString在URL encoding時(shí)都按照GBK編碼。
此時(shí)提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
很顯然,不同的瀏覽器以及同一瀏覽器的不同設(shè)置,會影響最終URL中PathInfo的編碼,該編碼可能不會由我們應(yīng)用來控制;對于queryString,則是可以由我們的應(yīng)用來完全控制的,對于上面的事例:中文的IE和FIREFOX都是采用GBK編碼queryString。
若調(diào)整下上例中的假設(shè)條件,設(shè)置Html內(nèi)content-type或meta中的charset=UTF-8;
此時(shí)在IE中queryString會按照UTF-8進(jìn)行編碼,即name=%E4%B8%AD%E5%9B%BD;
但是在非IE(Firefox、Chrome)中,此時(shí)提交時(shí)URL中會以中文直接提交,即name=中文;此時(shí)服務(wù)端的web服務(wù)器上肯定要進(jìn)行相應(yīng)的編碼配置,否則肯定會出現(xiàn)亂碼;
若設(shè)置Html內(nèi)content-type或meta中的charset=ISO-5899-1;
此時(shí)在IE、Firefox、Chrome中queryString都被用ISO-5899-1編碼了,即name= %26%2320013%3B%26%2322269%3B;
對于編碼串中的%26、%3B應(yīng)該是百分號編碼【Percent Encoding】中的保留字符,分別對應(yīng)&、;,兩者之間是經(jīng)過編碼的十進(jìn)制碼;對于這點(diǎn)偶也不是十分肯定?要是有同學(xué)比較清楚,請告訴偶下,thx。
POST方式提交:表單中的參數(shù)值對是通過request body發(fā)送給服務(wù)器,此時(shí)瀏覽器會根據(jù)網(wǎng)頁的ContentType("text/html; charset=GBK")中指定的編碼進(jìn)行對表單中的數(shù)據(jù)進(jìn)行編碼,然后發(fā)給服務(wù)器。
在服務(wù)器端的程序中我們可以通過Request.setCharacterEncoding() 設(shè)置編碼,然后通過request.getParameter獲得正確的數(shù)據(jù)。
小結(jié):
- 1. URL中的PathInfo字符串的編碼和解碼是由瀏覽器和應(yīng)用服務(wù)器的配置決定的,我們的程序不能設(shè)置,不要期望用request.setCharacterEncoding()方法能設(shè)置URL中pathInfo解碼時(shí)的字符集,這在servlet規(guī)范里面有說明。
- 2. URL中如果含有中文等非ASCII字符,則瀏覽器會對它們進(jìn)行URLEncode。為了避免瀏覽器采用了我們不希望的編碼,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode編碼過的字符串%xy;
對此建議URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
- 3. URL中PathInfo和QueryString應(yīng)該采用相同的編碼,pathInfo最好不要用中文;因?yàn)椴煌瑸g覽器對URL中PathInfo和QueryString編碼時(shí)采用的字符集不同,但應(yīng)用服務(wù)器對URL通常會采用相同的字符集來解碼。
- 4. URL中的queryString的編碼方式是依賴于網(wǎng)頁的contentType、meta中的charset字符集設(shè)置的,記住這點(diǎn)對于form get方式提交亂碼問題排查是非常有好處的。
- 5. 頁面文件的編碼最好和頁面內(nèi)設(shè)置的charset一致,因?yàn)槲覀兛赡茉陧撁鎯?nèi)部有很多中文,若編碼不一致,則可能出現(xiàn)在編輯器內(nèi)看著是正常的,但到了瀏覽器上則變成了亂碼(雖然頁面顯示為亂碼,但這并不意味著在form submit時(shí)數(shù)據(jù)會有問題,很可能出現(xiàn)負(fù)負(fù)為正的情況);這點(diǎn)在前面寶寶的文檔中也有說明;
另外,文件的編碼很可能會影響問題的排查;比如上例中我們就已說明:文件格式為ANSI/ASCII,若文件格式為Unicode/UTF-*系列,那測試結(jié)果會不會不一樣呢?大家可以自行測試下;
- 6. 服務(wù)端URL Encode時(shí)最好指定字符集,否則encoding的字符集依賴于本地系統(tǒng)的默認(rèn)編碼??梢詤⒖糺avadoc:
http://gceclub./Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
相關(guān)文檔參考:
字符,字節(jié)和編碼:http://www./zh/encoding.htm
各種字符集和編碼詳解:http://blog.csdn.net/ancky/archive/2008/01/11/2034809.aspx
深入淺出URL編碼:http://blog.csdn.net/yzhz/archive/2007/07/03/1676796.aspx
javascript html 相關(guān)編碼問題研究:http:///log/fpev3c89q.html
J2EE Web組件中中文及相關(guān)的問題(系列):http://blog.csdn.net/whodsow/archive/2003/10/27/19465.aspx