一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

中文化和國(guó)際化問(wèn)題權(quán)威解析之三:Java中文問(wèn)題分析...

 -ー意孤行ノ 2009-03-19
章從實(shí)際的中文問(wèn)題中,分析問(wèn)題的根本原因,以及解決之道。

注意,本章雖然著重說(shuō)明中文問(wèn)題,但本章所推出的結(jié)論卻是適合于世界所有語(yǔ)言文字的。

概述

我們?cè)趯?shí)際開發(fā)中碰到的中文問(wèn)題,真是形形色色,無(wú)法一一列舉。但是它們不是隨機(jī)產(chǎn)生的,而是有規(guī)律可循,有辦法解決的。

我們碰到最多的中文問(wèn)題,都發(fā)生在使用Java ServletWEB應(yīng)用時(shí)。其次,使用Java Mail API發(fā)送e-mail也會(huì)有類似的問(wèn)題。從表象上區(qū)分,大致上有以下幾種:

  1. 好端端的中文顯示成了問(wèn)號(hào)“?”,且一個(gè)中文變成2個(gè)問(wèn)號(hào)。
  2. 好端端的中文顯示成了問(wèn)號(hào)“?”,且一個(gè)中文變成1個(gè)問(wèn)號(hào)。
  3. 好端端的中文顯示成了看不懂的符號(hào),如“ÎÒ°®Alibaba”。
  4. WEB頁(yè)面中部分中文顯示正常,部分中文是亂碼。

要分析這些問(wèn)題的根本原因,首先要了解這些中文字符的輸入源,其次是了解這些字符被輸出到用戶瀏覽器經(jīng)過(guò)了哪些轉(zhuǎn)換和輸出環(huán)節(jié)。中文字符可以來(lái)源于:

  1. 程序內(nèi)嵌的中文,我們?cè)诔绦蚶镏苯訒鴮懼形淖址?/span>
  2. 文本文件,利用FileInputStream讀入文件內(nèi)容并轉(zhuǎn)換成字符。
  3. XML文件,利用XML解析器讀入內(nèi)存。
  4. 數(shù)據(jù)庫(kù),利用SQL查詢,取得的結(jié)果。
  5. 模板文件,例如VelocityWebMacro模板,我們使用模板生成WEB頁(yè)面。
  6. JSP頁(yè)面,在JSP生成的WEB頁(yè)面。
  7. 用戶通過(guò)瀏覽器提交的表單。

中文字符被裝入內(nèi)存以后,還要經(jīng)過(guò)若干個(gè)轉(zhuǎn)換和輸出環(huán)節(jié),最后才能到達(dá)用戶的瀏覽器被用戶看到。

  1. 文本通過(guò)response.getWriter()輸出到瀏覽器端。
  2. 瀏覽器讀取服務(wù)器的HTTP響應(yīng),并將響應(yīng)中包含的HTML頁(yè)面顯示在瀏覽器上。
  3. 文本可能被寫入XML文件、文本文件、數(shù)據(jù)庫(kù)中。

以上列舉的任何一個(gè)環(huán)節(jié)發(fā)生錯(cuò)誤,都可能產(chǎn)生亂碼現(xiàn)象。因此發(fā)生亂碼現(xiàn)象時(shí),不要慌,想想這個(gè)亂碼的文本是從哪里來(lái)的,又是以什么方式輸出的。

字符的輸入、轉(zhuǎn)換、輸出環(huán)節(jié)

內(nèi)嵌在程序代碼中的中文

因?yàn)?/span>Java源代碼(.java)本身是一個(gè)文本文件,所以和讀普通文本文件一樣,編譯器(javac)必須以字節(jié)流的方式讀入文件內(nèi)容,并以適當(dāng)?shù)木幋a轉(zhuǎn)換成Unicode字符而存儲(chǔ)在Java字節(jié)碼文件(.class)中。例如:Java源代碼文件中包含GBK編碼的中文字符,則使用下面的命令編譯:

  1. javac -encoding GBK MyClass.java

如果不指定-encoding參數(shù),javac會(huì)使用系統(tǒng)默認(rèn)的編碼:在中文Windows上,默認(rèn)是GBK,在英文Linux上,默認(rèn)是ISO-8859-1。因此,如果文件是在英文Linux下編譯而未指定-encoding,那么文件中的中文我愛Alibaba”就會(huì)變成“ÎÒ°®Alibaba”了。

從文本文件中讀入的字符

正如前面的 TestDecoding例子所示,在讀入文本文件時(shí),需要指定正確的編碼。如果不指明編碼,那么Java就會(huì)使用系統(tǒng)默認(rèn)的編碼來(lái)轉(zhuǎn)換文件中的字節(jié)流。下列代碼往往會(huì)產(chǎn)生問(wèn)題:

  1. public static String readStringFromFile(String filename) throws IOException {
  2.     // 正確的方法:
  3.     // FileInputStream istream = new FileInputStream(filename);
  4.     // InputStreamReader reader = new InputStreamReader(istream, charset);
  5.     
  6.     // 可能會(huì)導(dǎo)致亂碼的方法(取決于運(yùn)行平臺(tái)的默認(rèn)編碼):
  7.     FileReader reader = new FileReader(filename);
  8.     ...
  9. }
XML文件中讀入的字符

XML標(biāo)準(zhǔn)極為嚴(yán)格地遵守Unicode標(biāo)準(zhǔn)。XML文件的字符編碼是定義在XML文件中,而不是定義在XML解析器中的。如果不明確指定,任何標(biāo)準(zhǔn)的XML解析總是以UTF-8的方式解碼XML文件??梢杂孟旅娴姆绞皆?/span>XML文件中指定字符編碼:

  1. <?xml version="1.0" encoding="GB2312"?>

在解碼過(guò)程中,如果XML解析器發(fā)現(xiàn)一個(gè)非法的字節(jié),不會(huì)像Java一樣,轉(zhuǎn)換成問(wèn)號(hào)“?”,而是立即報(bào)錯(cuò)。所以XML解析器一般總會(huì)取得正確的Unicode字符。

注意

注意,XML規(guī)范并沒有定義GBKGB18030編碼,因此不能在XML文件使用這兩種編碼。目前可以使用的中文編碼是GB2312BIG5。相信這種情況以后會(huì)改變。如果確實(shí)想使用中文大字符集,請(qǐng)指定UTF-8作為XML文件的編碼。

數(shù)據(jù)庫(kù)

首先,數(shù)據(jù)庫(kù)一般都可以設(shè)置以何種字符編碼方式存儲(chǔ)文本;其次,數(shù)據(jù)庫(kù)的客戶端 —— JDBC驅(qū)動(dòng) —— 必須設(shè)置成和數(shù)據(jù)庫(kù)的內(nèi)置字符編碼一致;最后,盡可能使用UTF-8存取文本數(shù)據(jù),因?yàn)檫@樣可以在數(shù)據(jù)庫(kù)中方便地存儲(chǔ)所有國(guó)家的文字。

注意

我們AlibabaOracle數(shù)據(jù)庫(kù)目前采用7ASCII碼存儲(chǔ)文本(包括中文),這是一個(gè)極大的錯(cuò)誤,已經(jīng)導(dǎo)致了很多問(wèn)題。我們后面會(huì)講到。

模板文件

VelocityWebMacro是常用的Java模板系統(tǒng)。模板文件也是簡(jiǎn)單的文本文件。VelocityWebMacro都可以在各自的配置文件定義讀取模板所用的字符編碼方式。例如Velocity可以這樣設(shè)置:

  1. input.encoding=GBK

如果是在Turbine(一種基于MVC設(shè)計(jì)模式的WEB應(yīng)用框架,http://jakarta./turbine)中調(diào)用Velocity,可以在Turbine的配置文件中設(shè)置:

  1. services.VelocityService.input.encoding=GBK

這樣Velocity就可以用GBK編碼讀取模板文件。

JSP頁(yè)面

JSP是一種特殊的WEB頁(yè)面,在第一次使用時(shí),被自動(dòng)編譯成一個(gè)普通的servlet。在JSP的開頭指定JSP的字符編碼:

  1. <%@page contentType="text/html; charset=GBK"%>

上面這行告訴JSP

  1. javac -encoding GBK命令選項(xiàng)來(lái)編譯JSP所生成的servlet源代碼。

2.            使用GBK編碼輸出JSP servlet中的所有字符,相當(dāng)于:

  1. response.setContentType("text/html; charset=GBK")

3.            使用GBK解碼用戶的表單輸入,相當(dāng)于:

  1. request.setCharacterEncoding("GBK")
用戶通過(guò)瀏覽器提交的表單

瀏覽器是根據(jù)頁(yè)面的content type來(lái)決定以何種方式來(lái)編碼用戶輸入的表單的。例如,一個(gè)頁(yè)面的content typetext/html; charset=GBK,那么,當(dāng)用戶按下頁(yè)面中的submit按鈕時(shí),瀏覽器自動(dòng)將用戶的輸入用GBK方式編碼并發(fā)送回服務(wù)器端。服務(wù)器接到用戶的請(qǐng)求后,需要用正確的方式來(lái)解碼,方法是:

  1. request.setCharacterEncoding("GBK");

然后再調(diào)用request.getParameter(parameterName)時(shí)就可以得到正確的Unicode字符。

注意

必須在第一次調(diào)用request.getParameter(parameterName)之前調(diào)用request.setCharacterEncoding(charset),因?yàn)榻獯a是在第一次調(diào)用request.getParameter(parameterName)時(shí)發(fā)生的。

Servlet規(guī)范規(guī)定,如果沒有設(shè)定request.setCharacterEncoding,則使用ISO-8859-1來(lái)解碼用戶輸入的表單,而不是使用系統(tǒng)默認(rèn)的編碼。

對(duì)于multipart form(例如,上傳圖片的form表單),情況要復(fù)雜一些。因?yàn)?/span>servlet并沒有直接支持multipart form。所以大多數(shù)應(yīng)用程序使用了第三方的工具包來(lái)解析multipart form,例如:Oreilly COS工具包。然而,這些工具包大多使用系統(tǒng)默認(rèn)的編碼來(lái)解析用戶表單,和servlet規(guī)范不一致。如果你的servlet代碼沒有特別指明編碼方式,則兩種form表單將有不同的表現(xiàn),必有一種情況會(huì)出現(xiàn)亂碼現(xiàn)象。

Servlet輸出

Servlet可以用兩種方式向?yàn)g覽器輸出內(nèi)容:

  1. 字節(jié)流方式 —— 輸出到response.getOutputStream()。一般用來(lái)輸出二進(jìn)制內(nèi)容,例如圖片。
  2. 字符流方式 —— 輸出到response.getWriter()。用來(lái)輸出文本類型的內(nèi)容,如HTML和純文本。

在此我們只討論輸出文本的情形:response.getWriter()。在調(diào)用response.getWriter()前,我們必須設(shè)置content type

  1. response.setContentType("text/html; charset=GBK");

response.getWriter()通過(guò)content type中指定的字符編碼來(lái)決定如何將字符流轉(zhuǎn)換成字節(jié)流。

Turbine中,在配置文件中指定下面的內(nèi)容,Turbine會(huì)為你自動(dòng)設(shè)置content type

  1. locale.default.charset=GBK
瀏覽器如何確定頁(yè)面的字符編碼

瀏覽器收到從WEB服務(wù)器返回的頁(yè)面時(shí),

  1. 首先檢查HTTP響應(yīng)中指定的content type,也就是servlet通過(guò)response.setContentType方法設(shè)置的值。如果content type中指定字符編碼(例如text/html; charset=GBK),則使用這種方式解碼這個(gè)頁(yè)面。

2.            如果HTTP響應(yīng)中沒有指定字符集,那么瀏覽器會(huì)檢查HTML頁(yè)面中是否包含:

  1. <meta http-equiv="Content-Type" content="text/html; charset=GBK">

如果找到,則使用這里指定的字符編碼。

  1. 如果既沒有在HTTP響應(yīng)中指定字符編碼,也沒有在HTML內(nèi)容中指定字符編碼,則瀏覽器根據(jù)一定的規(guī)則自動(dòng)確定頁(yè)面的字符編碼。例如,在英文環(huán)境中,瀏覽器會(huì)使用ISO-8859-1,簡(jiǎn)體中文環(huán)境中,則使用GBK。用戶也可以根據(jù)自己的需要手工改變這一設(shè)置。
其它輸出環(huán)節(jié)

文本還可能被寫入XML文件、文本文件、數(shù)據(jù)庫(kù)中。類似的,輸出文件時(shí)一般都要指定字符編碼。如果不指定,通常Java會(huì)選擇系統(tǒng)默認(rèn)的編碼。這為程序運(yùn)行的結(jié)果產(chǎn)生了不確定因素。

亂碼分析

明白了各輸入、轉(zhuǎn)換、輸出環(huán)節(jié)是怎樣工作的,我們的分析工作就有頭緒了。在深入分析之前,有不少情況,觀察亂碼的表面現(xiàn)象就可以得到大概的結(jié)論。

一個(gè)中文變成了兩個(gè)問(wèn)號(hào)“?”

這個(gè)現(xiàn)象通常表明字符在輸入時(shí)出錯(cuò),也就是解碼錯(cuò)誤。

雖然輸出編碼是對(duì)的,但在此之前,由于錯(cuò)誤的輸入編碼,每個(gè)中文字變成了兩個(gè)不相干的歐洲字符。而這些歐洲字符的編碼和GBK編碼是相沖突的(但也不一定完全沖突,例如上例中的第三個(gè)字節(jié)B0,被轉(zhuǎn)換成GBKE3A1)。因此大部分中文被輸出成兩個(gè)問(wèn)號(hào)。

如果出現(xiàn)亂碼的中文字是從Velocity模板讀入的,說(shuō)明Velocity配置文件中的input.encoding設(shè)置不正確;如果這個(gè)中文字是從數(shù)據(jù)庫(kù)讀入的,說(shuō)明數(shù)據(jù)庫(kù)的配置出錯(cuò),也有可能文本在保存進(jìn)數(shù)據(jù)庫(kù)之前就已經(jīng)錯(cuò)了;如果這個(gè)中文字是從用戶表單輸入的,很可能是你忘了調(diào)用request.setCharacterEncoding("GBK")。

一個(gè)中文變成了一個(gè)問(wèn)號(hào)“?”

這個(gè)現(xiàn)象通常表明字符在輸出時(shí)出錯(cuò),也就是編碼錯(cuò)誤。

這很可能是因?yàn)闆]有設(shè)置response.setContentType("text/html; charset=GBK")。

中文顯示成了看不懂的符號(hào),如“ÎÒ°®Alibaba”

這個(gè)現(xiàn)象通常表明字符在輸入輸出時(shí)都出錯(cuò)了。

明眼人一看就發(fā)現(xiàn),實(shí)際上在這種情況下,最后輸出到瀏覽器上的字節(jié)流是正確的!只是因?yàn)?/span>content type被設(shè)成了錯(cuò)誤的ISO-8859-1編碼,所以才導(dǎo)致瀏覽器顯示不正確的。事實(shí)上,用戶可以手工改變?yōu)g覽器的設(shè)置,使瀏覽器使用GBK對(duì)字節(jié)流重新解碼。

看起來(lái)象是數(shù)學(xué)中的負(fù)負(fù)得正。為什么會(huì)這樣呢?這是因?yàn)?u>ISO-8859-1編碼的特殊性導(dǎo)致的。ISO-8859-1字符集的編碼范圍是0000-00FF,正好和一個(gè)字節(jié)的編碼范圍相對(duì)應(yīng)。這種特性保證了使用ISO-8859-1進(jìn)行編碼/解碼可以保持編碼數(shù)值不變。雖然中文字符在輸入JVM時(shí),被錯(cuò)誤地成了兩個(gè)歐洲字符,但由于輸出時(shí)也是用ISO-8859-1,結(jié)果被開的中文字的兩半又被神奇地合并在一起。

這種情形在英文版的Linux上最常發(fā)生,事實(shí)上我們公司的很多程序就是這樣做的。英文版Linux的系統(tǒng)默認(rèn)編碼為ISO-8859-1。假設(shè)我們的servlet從模板中生成動(dòng)態(tài)網(wǎng)頁(yè):

  • 輸入環(huán)節(jié) —— 如果我們不指定模板系統(tǒng)的字符編碼,那么,Java會(huì)使用系統(tǒng)默認(rèn)的編碼(ISO-8859-1)讀入模板文件,從而將一個(gè)GBK中文編碼看作兩個(gè)歐洲字符。
  • 輸出環(huán)節(jié) —— 如果我們?cè)谠O(shè)置response.setContentType("text/html")時(shí)不指明charset,按servlet規(guī)范,系統(tǒng)應(yīng)以ISO-8859-1輸出頁(yè)面。從而恢復(fù)了正確的字節(jié)流。
  • 瀏覽器輸入環(huán)節(jié) —— 瀏覽器發(fā)現(xiàn)HTTP響應(yīng)中未指定字符編碼,則檢查HTML中有沒有<meta http-equiv="Content-Type" content="text/html; charset=GBK">之類的標(biāo)記,如果有,則以GBK顯示頁(yè)面。此時(shí)頁(yè)面顯示是完全正常的(沒有亂碼)。
  • 如果瀏覽器發(fā)現(xiàn)HTTP響應(yīng)中未指定字符編碼,并且HTML中也沒有定義meta標(biāo)記,則使用系統(tǒng)默認(rèn)的編碼。這取決于運(yùn)行瀏覽器的平臺(tái)和瀏覽器的設(shè)置。一般英文平臺(tái)會(huì)以ISO-8859-1顯示頁(yè)面,從而顯示成亂碼。中文平臺(tái)有可能可以正確顯示頁(yè)面。

同樣的代碼,如果在中文Windows上運(yùn)行,因?yàn)橄到y(tǒng)默認(rèn)編碼為GBK,因而會(huì)轉(zhuǎn)變成一個(gè)中文變成一個(gè)問(wèn)號(hào)的情形。

如果頁(yè)面中有部分字符不是來(lái)源于模板,而是來(lái)源于XML文件或UTF-8編碼的數(shù)據(jù)庫(kù),又會(huì)轉(zhuǎn)變成WEB頁(yè)面中部分中文顯示正常,部分中文是亂碼的情形。

此外,把一個(gè)中文字符轉(zhuǎn)換成兩個(gè)歐洲字符,不僅使字符串變長(zhǎng)了一倍,影響效率,而且前面所說(shuō)的和Unicode相關(guān)的功能一概失效:斷句斷詞、排序、查看字符屬性、格式化日期和數(shù)字。

可見,使用這種方法顯示中文,引入了諸多不確定因素,實(shí)在不是一種可取的方法。但是很多程序員滿足于完成任務(wù),卻不求甚解,不理解Unicode的精義。甚至網(wǎng)上很多的文章也主張這么做,真是可悲可嘆。

WEB頁(yè)面中部分中文顯示正常,部分中文是亂碼

很明顯,這是由于同一頁(yè)面中的字符是從不同的輸入源取得的。假設(shè)有如下常見情形:

  • 從模板取得的中文字使用了系統(tǒng)默認(rèn)編碼,中文Windows上是GBK,英文Linux上是ISO-8859-1,后者將一個(gè)中文轉(zhuǎn)變成了兩個(gè)歐洲字符。
  • XML取得的中文字總是正確的Unicode字符。
  • 從用戶表單讀入的中文字,如果不指定編碼(request.setCharacterEncoding),總是以ISO-8859-1解碼。

結(jié)果就是:

Content Type

瀏覽器環(huán)境

服務(wù)器環(huán)境

從模板取得的中文字

XML取得的中文字

從用戶表單取得的中文字

text/html

中文Windows

中文Windows

一個(gè)中文變成1個(gè)問(wèn)號(hào)

一個(gè)中文變成1個(gè)問(wèn)號(hào)

碰巧正常

英文Linux

碰巧正常

一個(gè)中文變成1個(gè)問(wèn)號(hào)

碰巧正常

英文Windows

中文Windows

一個(gè)中文變成1個(gè)問(wèn)號(hào)

一個(gè)中文變成1個(gè)問(wèn)號(hào)

中文變成歐洲字符

英文Linux

中文變成歐洲字符

一個(gè)中文變成1個(gè)問(wèn)號(hào)

中文變成歐洲字符

text/html; charset=GBK

任意

中文Windows

正常

正常

一個(gè)中文變成2個(gè)問(wèn)號(hào)

英文Linux

一個(gè)中文變成2個(gè)問(wèn)號(hào)

正常

一個(gè)中文變成2個(gè)問(wèn)號(hào)

注意

所謂碰巧正常是指雖然在服務(wù)器上,一個(gè)中文被當(dāng)作兩個(gè)歐洲字符處理,但是輸出到瀏覽器以后,又被重新組合成了正確的字節(jié)序列,并且瀏覽器按默認(rèn)的選項(xiàng),會(huì)以中文GBK解碼此序列。對(duì)于中文變成歐洲字符的情形,可以在瀏覽器上人工設(shè)置字符編碼為GBK,或是在HTML中設(shè)置<meta http-equiv="Content-Type" content="text/html; charset=GBK">標(biāo)記,來(lái)顯示出中文。

深入分析

以上只是分析了最常見的亂碼現(xiàn)象。實(shí)際上,還可能會(huì)發(fā)生更復(fù)雜一點(diǎn)的情形。但是無(wú)論什么情形,都可以通過(guò)仔細(xì)分析中文字符經(jīng)過(guò)的每一個(gè)輸入、轉(zhuǎn)換、輸出環(huán)節(jié),來(lái)了解它的原因。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    色综合久久中文综合网| 久久精品视频就在久久| 中文字幕亚洲视频一区二区| 国产一区欧美一区二区| 日本二区三区在线播放| 亚洲第一视频少妇人妻系列| 成人国产一区二区三区精品麻豆| 国产欧美性成人精品午夜| 人人爽夜夜爽夜夜爽精品视频 | 欧美午夜不卡在线观看| 欧美韩日在线观看一区| 国产真人无遮挡免费视频一区| 大香伊蕉欧美一区二区三区| 国产欧美日产久久婷婷| 国产成人一区二区三区久久| 国产成人精品国产亚洲欧洲| 国语对白刺激高潮在线视频| 日韩欧美二区中文字幕| 国产美女网红精品演绎| 午夜午夜精品一区二区| 中文字幕亚洲在线一区| 精品欧美日韩一区二区三区| 99精品国产一区二区青青| 亚洲一区二区三区免费的视频| 人妻熟女中文字幕在线| 欧美国产亚洲一区二区三区| 欧美日韩综合免费视频| 国产精品欧美在线观看| 成人免费视频免费观看| 欧美日韩乱一区二区三区| 欧美乱妇日本乱码特黄大片| 国产剧情欧美日韩中文在线| 国产一区二区三中文字幕| 在线观看视频国产你懂的| 视频一区二区三区自拍偷| 日韩中文字幕欧美亚洲| 丰满少妇被猛烈插入在线观看| 国产老女人性生活视频| 久七久精品视频黄色的| 1024你懂的在线视频| 亚洲第一香蕉视频在线|