日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

中文乱码深入分析

發(fā)布時(shí)間:2023/12/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 中文乱码深入分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

幾種常見(jiàn)的編碼格式

為什么要編碼

不知道大家有沒(méi)有想過(guò)一個(gè)問(wèn)題,那就是為什么要編碼?我們能不能不編碼?要回答這個(gè)問(wèn)題必須要回到計(jì)算機(jī)是如何表示我們?nèi)祟?lèi)能夠理解的符號(hào)的,這些符號(hào)也就是我們?nèi)祟?lèi)使用的語(yǔ)言。由于人類(lèi)的語(yǔ)言有太多,因而表示這些語(yǔ)言的符號(hào)太多,無(wú)法用計(jì)算機(jī)中一個(gè)基本的存儲(chǔ)單元—— byte 來(lái)表示,因而必須要經(jīng)過(guò)拆分或一些翻譯工作,才能讓計(jì)算機(jī)能理解。我們可以把計(jì)算機(jī)能夠理解的語(yǔ)言假定為英語(yǔ),其它語(yǔ)言要能夠在計(jì)算機(jī)中使用必須經(jīng)過(guò)一次翻譯,把它翻譯成英語(yǔ)。這個(gè)翻譯的過(guò)程就是編碼。所以可以想象只要不是說(shuō)英語(yǔ)的國(guó)家要能夠使用計(jì)算機(jī)就必須要經(jīng)過(guò)編碼。這看起來(lái)有些霸道,但是這就是現(xiàn)狀,這也和我們國(guó)家現(xiàn)在在大力推廣漢語(yǔ)一樣,希望其它國(guó)家都會(huì)說(shuō)漢語(yǔ),以后其它的語(yǔ)言都翻譯成漢語(yǔ),我們可以把計(jì)算機(jī)中存儲(chǔ)信息的最小單位改成漢字,這樣我們就不存在編碼問(wèn)題了。

所以總的來(lái)說(shuō),編碼的原因可以總結(jié)為:

1, 計(jì)算機(jī)中存儲(chǔ)信息的最小單元是一個(gè)字節(jié)即 8 個(gè) bit,所以能表示的字符范圍是 0~255 個(gè)

2, 人類(lèi)要表示的符號(hào)太多,無(wú)法用一個(gè)字節(jié)來(lái)完全表示

3, 要解決這個(gè)矛盾必須需要一個(gè)新的數(shù)據(jù)結(jié)構(gòu) char,從 char 到 byte 必須編碼

如何翻譯

明白了各種語(yǔ)言需要交流,經(jīng)過(guò)翻譯是必要的,那又如何來(lái)翻譯呢?計(jì)算中提拱了多種翻譯方式,常見(jiàn)的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它們都可以被看作為字典,它們規(guī)定了轉(zhuǎn)化的規(guī)則,按照這個(gè)規(guī)則就可以讓計(jì)算機(jī)正確的表示我們的字符。目前的編碼格式很多,例如 GB2312、GBK、UTF-8、UTF-16 這幾種格式都可以表示一個(gè)漢字,那我們到底選擇哪種編碼格式來(lái)存儲(chǔ)漢字呢?這就要考慮到其它因素了,是存儲(chǔ)空間重要還是編碼的效率重要。根據(jù)這些因素來(lái)正確選擇編碼格式,下面簡(jiǎn)要介紹一下這幾種編碼格式。

  • ASCII 碼

學(xué)過(guò)計(jì)算機(jī)的人都知道 ASCII 碼,總共有 128 個(gè),用一個(gè)字節(jié)的低 7 位表示,0~31 是控制字符如換行回車(chē)刪除等;32~126 是打印字符,可以通過(guò)鍵盤(pán)輸入并且能夠顯示出來(lái)。

  • ISO-8859-1

128 個(gè)字符顯然是不夠用的,于是 ISO 組織在 ASCII 碼基礎(chǔ)上又制定了一些列標(biāo)準(zhǔn)用來(lái)擴(kuò)展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數(shù)西歐語(yǔ)言字符,所有應(yīng)用的最廣泛。ISO-8859-1 仍然是單字節(jié)編碼,它總共能表示 256 個(gè)字符。

  • GB2312

它的全稱(chēng)是《信息交換用漢字編碼字符集 基本集》,它是雙字節(jié)編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號(hào)區(qū),總共包含 682 個(gè)符號(hào),從 B0-F7 是漢字區(qū),包含 6763 個(gè)漢字。

  • GBK

全稱(chēng)叫《漢字內(nèi)碼擴(kuò)展規(guī)范》,是國(guó)家技術(shù)監(jiān)督局為 windows95 所制定的新的漢字內(nèi)碼規(guī)范,它的出現(xiàn)是為了擴(kuò)展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個(gè)碼位,它能表示 21003 個(gè)漢字,它的編碼是和 GB2312 兼容的,也就是說(shuō)用 GB2312 編碼的漢字可以用 GBK 來(lái)解碼,并且不會(huì)有亂碼。

  • GB18030

全稱(chēng)是《信息交換用漢字編碼字符集》,是我國(guó)的強(qiáng)制標(biāo)準(zhǔn),它可能是單字節(jié)、雙字節(jié)或者四字節(jié)編碼,它的編碼與 GB2312 編碼兼容,這個(gè)雖然是國(guó)家標(biāo)準(zhǔn),但是實(shí)際應(yīng)用系統(tǒng)中使用的并不廣泛。

  • UTF-16

說(shuō)到 UTF 必須要提到 Unicode(Universal Code 統(tǒng)一碼),ISO 試圖想創(chuàng)建一個(gè)全新的超語(yǔ)言字典,世界上所有的語(yǔ)言都可以通過(guò)這本字典來(lái)相互翻譯。可想而知這個(gè)字典是多么的復(fù)雜,關(guān)于 Unicode 的詳細(xì)規(guī)范可以參考相應(yīng)文檔。Unicode 是 Java 和 XML 的基礎(chǔ),下面詳細(xì)介紹 Unicode 在計(jì)算機(jī)中的存儲(chǔ)形式。

UTF-16 具體定義了 Unicode 字符在計(jì)算機(jī)中存取方法。UTF-16 用兩個(gè)字節(jié)來(lái)表示 Unicode 轉(zhuǎn)化格式,這個(gè)是定長(zhǎng)的表示方法,不論什么字符都可以用兩個(gè)字節(jié)表示,兩個(gè)字節(jié)是 16 個(gè) bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每?jī)蓚€(gè)字節(jié)表示一個(gè)字符,這個(gè)在字符串操作時(shí)就大大簡(jiǎn)化了操作,這也是 Java 以 UTF-16 作為內(nèi)存的字符存儲(chǔ)格式的一個(gè)很重要的原因。

  • UTF-8

UTF-16 統(tǒng)一采用兩個(gè)字節(jié)表示一個(gè)字符,雖然在表示上非常簡(jiǎn)單方便,但是也有其缺點(diǎn),有很大一部分字符用一個(gè)字節(jié)就可以表示的現(xiàn)在要兩個(gè)字節(jié)表示,存儲(chǔ)空間放大了一倍,在現(xiàn)在的網(wǎng)絡(luò)帶寬還非常有限的今天,這樣會(huì)增大網(wǎng)絡(luò)傳輸?shù)牧髁?#xff0c;而且也沒(méi)必要。而 UTF-8 采用了一種變長(zhǎng)技術(shù),每個(gè)編碼區(qū)域有不同的字碼長(zhǎng)度。不同類(lèi)型的字符可以是由 1~6 個(gè)字節(jié)組成。

UTF-8 有以下編碼規(guī)則:

1, 如果一個(gè)字節(jié),最高位(第 8 位)為 0,表示這是一個(gè) ASCII 字符(00 - 7F)。可見(jiàn),所有 ASCII 編碼已經(jīng)是 UTF-8 了。

2, 如果一個(gè)字節(jié),以 11 開(kāi)頭,連續(xù)的 1 的個(gè)數(shù)暗示這個(gè)字符的字節(jié)數(shù),例如:110xxxxx 代表它是雙字節(jié) UTF-8 字符的首字節(jié)。

3, 如果一個(gè)字節(jié),以 10 開(kāi)始,表示它不是首字節(jié),需要向前查找才能得到當(dāng)前字符的首字節(jié)

Java 中需要編碼的場(chǎng)景

前面描述了常見(jiàn)的幾種編碼格式,下面將介紹 Java 中如何處理對(duì)編碼的支持,什么場(chǎng)合中需要編碼。

I/O 操作中存在的編碼

我們知道涉及到編碼的地方一般都在字符到字節(jié)或者字節(jié)到字符的轉(zhuǎn)換上,而需要這種轉(zhuǎn)換的場(chǎng)景主要是在 I/O 的時(shí)候,這個(gè) I/O 包括磁盤(pán) I/O 和網(wǎng)絡(luò) I/O,關(guān)于網(wǎng)絡(luò) I/O 部分在后面將主要以 Web 應(yīng)用為例介紹。下圖是 Java 中處理 I/O 問(wèn)題的接口:

?

Reader 類(lèi)是 Java I/O 中讀字符的父類(lèi),而 InputStream 類(lèi)是讀字節(jié)的父類(lèi),InputStreamReader 類(lèi)就是關(guān)聯(lián)字節(jié)到字符的橋梁,它負(fù)責(zé)在 I/O 過(guò)程中處理讀取字節(jié)到字符的轉(zhuǎn)換,而具體字節(jié)到字符的解碼實(shí)現(xiàn)它由 StreamDecoder 去實(shí)現(xiàn),在 StreamDecoder 解碼過(guò)程中必須由用戶(hù)指定 Charset 編碼格式。值得注意的是如果你沒(méi)有指定 Charset,將使用本地環(huán)境中的默認(rèn)字符集,例如在中文環(huán)境中將使用 GBK 編碼。

寫(xiě)的情況也是類(lèi)似,字符的父類(lèi)是 Writer,字節(jié)的父類(lèi)是 OutputStream,通過(guò) OutputStreamWriter 轉(zhuǎn)換字符到字節(jié)。如下圖所示:

?

同樣 StreamEncoder 類(lèi)負(fù)責(zé)將字符編碼成字節(jié),編碼格式和默認(rèn)編碼規(guī)則與解碼是一致的。

如下面一段代碼,實(shí)現(xiàn)了文件的讀寫(xiě)功能:

清單 1.I/O 涉及的編碼示例

?String file = "c:/stream.txt";

?String charset = "UTF-8";

?// 寫(xiě)字符換轉(zhuǎn)成字節(jié)流

?FileOutputStream outputStream = new FileOutputStream(file);

?OutputStreamWriter writer = new OutputStreamWriter(

?outputStream, charset);

?try {

??? writer.write("這是要保存的中文字符");

?} finally {

??? writer.close();

?}

?// 讀取字節(jié)轉(zhuǎn)換成字符

?FileInputStream inputStream = new FileInputStream(file);

?InputStreamReader reader = new InputStreamReader(

?inputStream, charset);

?StringBuffer buffer = new StringBuffer();

?char[] buf = new char[64];

?int count = 0;

?try {

??? while ((count = reader.read(buf)) != -1) {

??????? buffer.append(buffer, 0, count);

??? }

?} finally {

??? reader.close();

?}

在我們的應(yīng)用程序中涉及到 I/O 操作時(shí)只要注意指定統(tǒng)一的編解碼 Charset 字符集,一般不會(huì)出現(xiàn)亂碼問(wèn)題,有些應(yīng)用程序如果不注意指定字符編碼,中文環(huán)境中取操作系統(tǒng)默認(rèn)編碼,如果編解碼都在中文環(huán)境中,通常也沒(méi)問(wèn)題,但是還是強(qiáng)烈的不建議使用操作系統(tǒng)的默認(rèn)編碼,因?yàn)檫@樣,你的應(yīng)用程序的編碼格式就和運(yùn)行環(huán)境綁定起來(lái)了,在跨環(huán)境下很可能出現(xiàn)亂碼問(wèn)題。

內(nèi)存中操作中的編碼

在 Java 開(kāi)發(fā)中除了 I/O 涉及到編碼外,最常用的應(yīng)該就是在內(nèi)存中進(jìn)行字符到字節(jié)的數(shù)據(jù)類(lèi)型的轉(zhuǎn)換,Java 中用 String 表示字符串,所以 String 類(lèi)就提供轉(zhuǎn)換到字節(jié)的方法,也支持將字節(jié)轉(zhuǎn)換為字符串的構(gòu)造函數(shù)。如下代碼示例:

?String s = "這是一段中文字符串";

?byte[] b = s.getBytes("UTF-8");

?String n = new String(b,"UTF-8");

另外一個(gè)是已經(jīng)被被廢棄的 ByteToCharConverter 和 CharToByteConverter 類(lèi),它們分別提供了 convertAll 方法可以實(shí)現(xiàn) byte[] 和 char[] 的互轉(zhuǎn)。如下代碼所示:

?ByteToCharConverter charConverter = ByteToCharConverter.getConverter("UTF-8");

?char c[] = charConverter.convertAll(byteArray);

?CharToByteConverter byteConverter = CharToByteConverter.getConverter("UTF-8");

?byte[] b = byteConverter.convertAll(c);

這兩個(gè)類(lèi)已經(jīng)被 Charset 類(lèi)取代,Charset 提供 encode 與 decode 分別對(duì)應(yīng) char[] 到 byte[] 的編碼和 byte[] 到 char[] 的解碼。如下代碼所示:

?Charset charset = Charset.forName("UTF-8");

?ByteBuffer byteBuffer = charset.encode(string);

?CharBuffer charBuffer = charset.decode(byteBuffer);

編碼與解碼都在一個(gè)類(lèi)中完成,通過(guò) forName 設(shè)置編解碼字符集,這樣更容易統(tǒng)一編碼格式,比 ByteToCharConverter 和 CharToByteConverter 類(lèi)更方便。

Java 中還有一個(gè) ByteBuffer 類(lèi),它提供一種 char 和 byte 之間的軟轉(zhuǎn)換,它們之間轉(zhuǎn)換不需要編碼與解碼,只是把一個(gè) 16bit 的 char 格式,拆分成為 2 個(gè) 8bit 的 byte 表示,它們的實(shí)際值并沒(méi)有被修改,僅僅是數(shù)據(jù)的類(lèi)型做了轉(zhuǎn)換。如下代碼所以:

?ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024);

?ByteBuffer byteBuffer = heapByteBuffer.putChar(c);

以上這些提供字符和字節(jié)之間的相互轉(zhuǎn)換只要我們?cè)O(shè)置編解碼格式統(tǒng)一一般都不會(huì)出現(xiàn)問(wèn)題。

回頁(yè)首

Java 中如何編解碼

前面介紹了幾種常見(jiàn)的編碼格式,這里將以實(shí)際例子介紹 Java 中如何實(shí)現(xiàn)編碼及解碼,下面我們以“I am 君山”這個(gè)字符串為例介紹 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 編碼格式進(jìn)行編碼的。

清單 2.String 編碼

?public static void encode() {

??????? String name = "I am 君山";

??????? toHex(name.toCharArray());

??????? try {

??????????? byte[] iso8859 = name.getBytes("ISO-8859-1");

??????????? toHex(iso8859);

??????????? byte[] gb2312 = name.getBytes("GB2312");

??????????? toHex(gb2312);

??????????? byte[] gbk = name.getBytes("GBK");

??????????? toHex(gbk);

??????????? byte[] utf16 = name.getBytes("UTF-16");

??????????? toHex(utf16);

??????????? byte[] utf8 = name.getBytes("UTF-8");

??????????? toHex(utf8);

??????? } catch (UnsupportedEncodingException e) {

??????????? e.printStackTrace();

??????? }

?}

我們把 name 字符串按照前面說(shuō)的幾種編碼格式進(jìn)行編碼轉(zhuǎn)化成 byte 數(shù)組,然后以 16 進(jìn)制輸出,我們先看一下 Java 是如何進(jìn)行編碼的。

下面是 Java 中編碼需要用到的類(lèi)圖

1. Java 編碼類(lèi)圖

?

首先根據(jù)指定的 charsetName 通過(guò) Charset.forName(charsetName) 設(shè)置 Charset 類(lèi),然后根據(jù) Charset 創(chuàng)建 CharsetEncoder 對(duì)象,再調(diào)用 CharsetEncoder.encode 對(duì)字符串進(jìn)行編碼,不同的編碼類(lèi)型都會(huì)對(duì)應(yīng)到一個(gè)類(lèi)中,實(shí)際的編碼過(guò)程是在這些類(lèi)中完成的。下面是 String. getBytes(charsetName) 編碼過(guò)程的時(shí)序圖

2.Java 編碼時(shí)序圖

?

從上圖可以看出根據(jù) charsetName 找到 Charset 類(lèi),然后根據(jù)這個(gè)字符集編碼生成 CharsetEncoder,這個(gè)類(lèi)是所有字符編碼的父類(lèi),針對(duì)不同的字符編碼集在其子類(lèi)中定義了如何實(shí)現(xiàn)編碼,有了 CharsetEncoder 對(duì)象后就可以調(diào)用 encode 方法去實(shí)現(xiàn)編碼了。這個(gè)是 String.getBytes 編碼方法,其它的如 StreamEncoder 中也是類(lèi)似的方式。下面看看不同的字符集是如何將前面的字符串編碼成 byte 數(shù)組的?

如字符串“I am 君山”的 char 數(shù)組為 49 20 61 6d 20 541b 5c71,下面把它按照不同的編碼格式轉(zhuǎn)化成相應(yīng)的字節(jié)。

按照 ISO-8859-1 編碼

字符串“I am 君山”用 ISO-8859-1 編碼,下面是編碼結(jié)果:

?

從上圖看出 7 個(gè) char 字符經(jīng)過(guò) ISO-8859-1 編碼轉(zhuǎn)變成 7 個(gè) byte 數(shù)組,ISO-8859-1 是單字節(jié)編碼,中文君山被轉(zhuǎn)化成值是 3f byte3f 也就是字符,所以經(jīng)常會(huì)出現(xiàn)中文變成很可能就是錯(cuò)誤的使用了 ISO-8859-1 這個(gè)編碼導(dǎo)致的。中文字符經(jīng)過(guò) ISO-8859-1 編碼會(huì)丟失信息,通常我們稱(chēng)之為黑洞,它會(huì)把不認(rèn)識(shí)的字符吸收掉。由于現(xiàn)在大部分基礎(chǔ)的 Java 框架或系統(tǒng)默認(rèn)的字符集編碼都是 ISO-8859-1,所以很容易出現(xiàn)亂碼問(wèn)題,后面將會(huì)分析不同的亂碼形式是怎么出現(xiàn)的。

按照 GB2312 編碼

字符串“I am 君山”用 GB2312 編碼,下面是編碼結(jié)果:

?

GB2312 對(duì)應(yīng)的 Charset sun.nio.cs.ext. EUC_CN 而對(duì)應(yīng)的 CharsetDecoder 編碼類(lèi)是 sun.nio.cs.ext. DoubleByteGB2312 字符集有一個(gè) char byte 的碼表,不同的字符編碼就是查這個(gè)碼表找到與每個(gè)字符的對(duì)應(yīng)的字節(jié),然后拼裝成 byte 數(shù)組。查表的規(guī)則如下:

?c2b[c2bIndex[char >> 8] + (char & 0xff)]

如果查到的碼位值大于 oxff 則是雙字節(jié),否則是單字節(jié)。雙字節(jié)高 8 位作為第一個(gè)字節(jié),低 8 位作為第二個(gè)字節(jié),如下代碼所示:

?if (bb > 0xff) {??? // DoubleByte

??????????? if (dl - dp < 2)

??????????????? return CoderResult.OVERFLOW;

??????????? da[dp++] = (byte) (bb >> 8);

??????????? da[dp++] = (byte) bb;

?} else {????????????????????? // SingleByte

??????????? if (dl - dp < 1)

??????????????? return CoderResult.OVERFLOW;

??????????? da[dp++] = (byte) bb;

?}

從上圖可以看出前 5 個(gè)字符經(jīng)過(guò)編碼后仍然是 5 個(gè)字節(jié),而漢字被編碼成雙字節(jié),在第一節(jié)中介紹到 GB2312 只支持 6763 個(gè)漢字,所以并不是所有漢字都能夠用 GB2312 編碼。

按照 GBK 編碼

字符串“I am 君山”用 GBK 編碼,下面是編碼結(jié)果:

?

你可能已經(jīng)發(fā)現(xiàn)上圖與 GB2312 編碼的結(jié)果是一樣的,沒(méi)錯(cuò) GBK GB2312 編碼結(jié)果是一樣的,由此可以得出 GBK 編碼是兼容 GB2312 編碼的,它們的編碼算法也是一樣的。不同的是它們的碼表長(zhǎng)度不一樣,GBK 包含的漢字字符更多。所以只要是經(jīng)過(guò) GB2312 編碼的漢字都可以用 GBK 進(jìn)行解碼,反過(guò)來(lái)則不然。

按照 UTF-16 編碼

字符串“I am 君山”用 UTF-16 編碼,下面是編碼結(jié)果:

?

UTF-16 編碼將 char 數(shù)組放大了一倍,單字節(jié)范圍內(nèi)的字符,在高位補(bǔ) 0 變成兩個(gè)字節(jié),中文字符也變成兩個(gè)字節(jié)。從 UTF-16 編碼規(guī)則來(lái)看,僅僅將字符的高位和地位進(jìn)行拆分變成兩個(gè)字節(jié)。特點(diǎn)是編碼效率非常高,規(guī)則很簡(jiǎn)單,由于不同處理器對(duì) 2 字節(jié)處理方式不同,Big-endian(高位字節(jié)在前,低位字節(jié)在后)或 Little-endian(低位字節(jié)在前,高位字節(jié)在后)編碼,所以在對(duì)一串字符串進(jìn)行編碼是需要指明到底是 Big-endian 還是 Little-endian,所以前面有兩個(gè)字節(jié)用來(lái)保存 BYTE_ORDER_MARK 值,UTF-16 是用定長(zhǎng) 16 位(2 字節(jié))來(lái)表示的 UCS-2 Unicode 轉(zhuǎn)換格式,通過(guò)代理對(duì)來(lái)訪(fǎng)問(wèn) BMP 之外的字符編碼。

按照 UTF-8 編碼

字符串“I am 君山”用 UTF-8 編碼,下面是編碼結(jié)果:

?

UTF-16 雖然編碼效率很高,但是對(duì)單字節(jié)范圍內(nèi)字符也放大了一倍,這無(wú)形也浪費(fèi)了存儲(chǔ)空間,另外 UTF-16 采用順序編碼,不能對(duì)單個(gè)字符的編碼值進(jìn)行校驗(yàn),如果中間的一個(gè)字符碼值損壞,后面的所有碼值都將受影響。而 UTF-8 這些問(wèn)題都不存在,UTF-8 對(duì)單字節(jié)范圍內(nèi)字符仍然用一個(gè)字節(jié)表示,對(duì)漢字采用三個(gè)字節(jié)表示。它的編碼規(guī)則如下:

清單 3.UTF-8 編碼代碼片段

?private CoderResult encodeArrayLoop(CharBuffer src,

?ByteBuffer dst){

??????????? char[] sa = src.array();

??????????? int sp = src.arrayOffset() + src.position();

??????????? int sl = src.arrayOffset() + src.limit();

??????????? byte[] da = dst.array();

??????????? int dp = dst.arrayOffset() + dst.position();

??????????? int dl = dst.arrayOffset() + dst.limit();

??????????? int dlASCII = dp + Math.min(sl - sp, dl - dp);

??????????? // ASCII only loop

??????????? while (dp < dlASCII && sa[sp] < '\u0080')

??????????????? da[dp++] = (byte) sa[sp++];

??????????? while (sp < sl) {

??????????????? char c = sa[sp];

??????????????? if (c < 0x80) {

??????????????????? // Have at most seven bits

??????????????????? if (dp >= dl)

??????????????????????? return overflow(src, sp, dst, dp);

??????????????????? da[dp++] = (byte)c;

??????????????? } else if (c < 0x800) {

??????????????????? // 2 bytes, 11 bits

??????????????????? if (dl - dp < 2)

??????????????????????? return overflow(src, sp, dst, dp);

??????????????????? da[dp++] = (byte)(0xc0 | (c >> 6));

??????????????????? da[dp++] = (byte)(0x80 | (c & 0x3f));

??????????????? } else if (Character.isSurrogate(c)) {

??????????????????? // Have a surrogate pair

??????????????????? if (sgp == null)

??????????????????????? sgp = new Surrogate.Parser();

??????????????????? int uc = sgp.parse(c, sa, sp, sl);

??????????????????? if (uc < 0) {

??????????????????????? updatePositions(src, sp, dst, dp);

??????????????????????? return sgp.error();

???? ???????????????}

??????????????????? if (dl - dp < 4)

??????????????????????? return overflow(src, sp, dst, dp);

??????????????????? da[dp++] = (byte)(0xf0 | ((uc >> 18)));

??????????????????? da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));

?????????? ?????????da[dp++] = (byte)(0x80 | ((uc >>? 6) & 0x3f));

??????????????????? da[dp++] = (byte)(0x80 | (uc & 0x3f));

??????????????????? sp++;? // 2 chars

??????????????? } else {

??????????????????? // 3 bytes, 16 bits

??????????????????? if (dl - dp < 3)

??????????????????????? return overflow(src, sp, dst, dp);

??????????????????? da[dp++] = (byte)(0xe0 | ((c >> 12)));

??????????????????? da[dp++] = (byte)(0x80 | ((c >>? 6) & 0x3f));

??????????????????? da[dp++] = (byte)(0x80 | (c & 0x3f));

???? ???????????}

??????????????? sp++;

??????????? }

??????????? updatePositions(src, sp, dst, dp);

??????????? return CoderResult.UNDERFLOW;

?}

UTF-8 編碼與 GBK 和 GB2312 不同,不用查碼表,所以在編碼效率上 UTF-8 的效率會(huì)更好,所以在存儲(chǔ)中文字符時(shí) UTF-8 編碼比較理想。

幾種編碼格式的比較

對(duì)中文字符后面四種編碼格式都能處理,GB2312 與 GBK 編碼規(guī)則類(lèi)似,但是 GBK 范圍更大,它能處理所有漢字字符,所以 GB2312 與 GBK 比較應(yīng)該選擇 GBK。UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規(guī)則不太相同,相對(duì)來(lái)說(shuō) UTF-16 編碼效率最高,字符到字節(jié)相互轉(zhuǎn)換更簡(jiǎn)單,進(jìn)行字符串操作也更好。它適合在本地磁盤(pán)和內(nèi)存之間使用,可以進(jìn)行字符和字節(jié)之間快速切換,如 Java 的內(nèi)存編碼就是采用 UTF-16 編碼。但是它不適合在網(wǎng)絡(luò)之間傳輸,因?yàn)榫W(wǎng)絡(luò)傳輸容易損壞字節(jié)流,一旦字節(jié)流損壞將很難恢復(fù),想比較而言 UTF-8 更適合網(wǎng)絡(luò)傳輸,對(duì) ASCII 字符采用單字節(jié)存儲(chǔ),另外單個(gè)字符損壞也不會(huì)影響后面其它字符,在編碼效率上介于 GBK 和 UTF-16 之間,所以 UTF-8 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

回頁(yè)首

Java Web 涉及到的編碼

對(duì)于使用中文來(lái)說(shuō),有 I/O 的地方就會(huì)涉及到編碼,前面已經(jīng)提到了 I/O 操作會(huì)引起編碼,而大部分 I/O 引起的亂碼都是網(wǎng)絡(luò) I/O,因?yàn)楝F(xiàn)在幾乎所有的應(yīng)用程序都涉及到網(wǎng)絡(luò)操作,而數(shù)據(jù)經(jīng)過(guò)網(wǎng)絡(luò)傳輸都是以字節(jié)為單位的,所以所有的數(shù)據(jù)都必須能夠被序列化為字節(jié)。在 Java 中數(shù)據(jù)被序列化必須繼承 Serializable 接口。

這里有一個(gè)問(wèn)題,你是否認(rèn)真考慮過(guò)一段文本它的實(shí)際大小應(yīng)該怎么計(jì)算,我曾經(jīng)碰到過(guò)一個(gè)問(wèn)題:就是要想辦法壓縮 Cookie 大小,減少網(wǎng)絡(luò)傳輸量,當(dāng)時(shí)有選擇不同的壓縮算法,發(fā)現(xiàn)壓縮后字符數(shù)是減少了,但是并沒(méi)有減少字節(jié)數(shù)。所謂的壓縮只是將多個(gè)單字節(jié)字符通過(guò)編碼轉(zhuǎn)變成一個(gè)多字節(jié)字符。減少的是 String.length(),而并沒(méi)有減少最終的字節(jié)數(shù)。例如將“ab”兩個(gè)字符通過(guò)某種編碼轉(zhuǎn)變成一個(gè)奇怪的字符,雖然字符數(shù)從兩個(gè)變成一個(gè),但是如果采用 UTF-8 編碼這個(gè)奇怪的字符最后經(jīng)過(guò)編碼可能又會(huì)變成三個(gè)或更多的字節(jié)。同樣的道理比如整型數(shù)字 1234567 如果當(dāng)成字符來(lái)存儲(chǔ),采用 UTF-8 來(lái)編碼占用 7 個(gè) byte,采用 UTF-16 編碼將會(huì)占用 14 個(gè) byte,但是把它當(dāng)成 int 型數(shù)字來(lái)存儲(chǔ)只需要 4 個(gè) byte 來(lái)存儲(chǔ)。所以看一段文本的大小,看字符本身的長(zhǎng)度是沒(méi)有意義的,即使是一樣的字符采用不同的編碼最終存儲(chǔ)的大小也會(huì)不同,所以從字符到字節(jié)一定要看編碼類(lèi)型。

另外一個(gè)問(wèn)題,你是否考慮過(guò),當(dāng)我們?cè)陔娔X中某個(gè)文本編輯器里輸入某個(gè)漢字時(shí),它到底是怎么表示的?我們知道,計(jì)算機(jī)里所有的信息都是以 01 表示的,那么一個(gè)漢字,它到底是多少個(gè) 0 和 1 呢?我們能夠看到的漢字都是以字符形式出現(xiàn)的,例如在 Java 中“淘寶”兩個(gè)字符,它在計(jì)算機(jī)中的數(shù)值 10 進(jìn)制是 28120 和 23453,16 進(jìn)制是 6bd8 和 5d9d,也就是這兩個(gè)字符是由這兩個(gè)數(shù)字唯一表示的。Java 中一個(gè) char 是 16 個(gè) bit 相當(dāng)于兩個(gè)字節(jié),所以?xún)蓚€(gè)漢字用 char 表示在內(nèi)存中占用相當(dāng)于四個(gè)字節(jié)的空間。

這兩個(gè)問(wèn)題搞清楚后,我們看一下 Java Web 中那些地方可能會(huì)存在編碼轉(zhuǎn)換?

用戶(hù)從瀏覽器端發(fā)起一個(gè) HTTP 請(qǐng)求,需要存在編碼的地方是 URL、Cookie、Parameter。服務(wù)器端接受到 HTTP 請(qǐng)求后要解析 HTTP 協(xié)議,其中 URI、Cookie 和 POST 表單參數(shù)需要解碼,服務(wù)器端可能還需要讀取數(shù)據(jù)庫(kù)中的數(shù)據(jù),本地或網(wǎng)絡(luò)中其它地方的文本文件,這些數(shù)據(jù)都可能存在編碼問(wèn)題,當(dāng) Servlet 處理完所有請(qǐng)求的數(shù)據(jù)后,需要將這些數(shù)據(jù)再編碼通過(guò) Socket 發(fā)送到用戶(hù)請(qǐng)求的瀏覽器里,再經(jīng)過(guò)瀏覽器解碼成為文本。這些過(guò)程如下圖所示:

3. 一次 HTTP 請(qǐng)求的編碼示例(查看大圖

?

如上圖所示一次 HTTP 請(qǐng)求設(shè)計(jì)到很多地方需要編解碼,它們編解碼的規(guī)則是什么?下面將會(huì)重點(diǎn)闡述一下:

URL 的編解碼

用戶(hù)提交一個(gè) URL,這個(gè) URL 中可能存在中文,因此需要編碼,如何對(duì)這個(gè) URL 進(jìn)行編碼?根據(jù)什么規(guī)則來(lái)編碼?有如何來(lái)解碼?如下圖一個(gè) URL:

4.URL 的幾個(gè)組成部分

?

上圖中以 Tomcat 作為 Servlet Engine 為例,它們分別對(duì)應(yīng)到下面這些配置文件中:

Port 對(duì)應(yīng)在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 應(yīng)用的 web.xml 中的

?<servlet-mapping>

??????? <servlet-name>junshanExample</servlet-name>

??????? <url-pattern>/servlets/servlet/*</url-pattern>

?</servlet-mapping>

<url-pattern> 中配置,PathInfo 是我們請(qǐng)求的具體的 Servlet,QueryString 是要傳遞的參數(shù),注意這里是在瀏覽器里直接輸入 URL 所以是通過(guò) Get 方法請(qǐng)求的,如果是 POST 方法請(qǐng)求的話(huà),QueryString 將通過(guò)表單方式提交到服務(wù)器端,這個(gè)將在后面再介紹。

上圖中 PathInfo 和 QueryString 出現(xiàn)了中文,當(dāng)我們?cè)跒g覽器中直接輸入這個(gè) URL 時(shí),在瀏覽器端和服務(wù)端會(huì)如何編碼和解析這個(gè) URL 呢?為了驗(yàn)證瀏覽器是怎么編碼 URL 的我們選擇 FireFox 瀏覽器并通過(guò) HTTPFox 插件觀(guān)察我們請(qǐng)求的 URL 的實(shí)際的內(nèi)容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的測(cè)試結(jié)果

5. HTTPFox 的測(cè)試結(jié)果

?

君山的編碼結(jié)果分別是:e5 90 9b e5 b1 b1,be fd c9 bd,查閱上一屆的編碼可知,PathInfo 是 UTF-8 編碼而 QueryString 是經(jīng)過(guò) GBK 編碼,至于為什么會(huì)有“%”?查閱 URL 的編碼規(guī)范 RFC3986 可知瀏覽器編碼 URL 是將非 ASCII 字符按照某種編碼格式編碼成 16 進(jìn)制數(shù)字然后將每個(gè) 16 進(jìn)制表示的字節(jié)前加上“%”,所以最終的 URL 就成了上圖的格式了。

默認(rèn)情況下中文 IE 最終的編碼結(jié)果也是一樣的,不過(guò) IE 瀏覽器可以修改 URL 的編碼格式在選項(xiàng) -> 高級(jí) -> 國(guó)際里面的發(fā)送 UTF-8 URL 選項(xiàng)可以取消。

從上面測(cè)試結(jié)果可知瀏覽器對(duì) PathInfo 和 QueryString 的編碼是不一樣的,不同瀏覽器對(duì) PathInfo 也可能不一樣,這就對(duì)服務(wù)器的解碼造成很大的困難,下面我們以 Tomcat 為例看一下,Tomcat 接受到這個(gè) URL 是如何解碼的。

解析請(qǐng)求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個(gè)方法把傳過(guò)來(lái)的 URL 的 byte[] 設(shè)置到 org.apache.coyote.Request 的相應(yīng)的屬性中。這里的 URL 仍然是 byte 格式,轉(zhuǎn)成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

?protected void convertURI(MessageBytes uri, Request request)

?throws Exception {

??????? ByteChunk bc = uri.getByteChunk();

??????? int length = bc.getLength();

??????? CharChunk cc = uri.getCharChunk();

??????? cc.allocate(length, -1);

??????? String enc = connector.getURIEncoding();

??????? if (enc != null) {

??????????? B2CConverter conv = request.getURIConverter();

??????????? try {

??????????????? if (conv == null) {

??????????????????? conv = new B2CConverter(enc);

??????????????????? request.setURIConverter(conv);

??????????????? }

?? ?????????} catch (IOException e) {...}

??????????? if (conv != null) {

??????????????? try {

??????????????????? conv.convert(bc, cc, cc.getBuffer().length -

?cc.getEnd());

??????????????????? uri.setChars(cc.getBuffer(), cc.getStart(),

?cc.getLength());

??????????????????? return;

??????????????? } catch (IOException e) {...}

??????????? }

??????? }

??????? // Default encoding: fast conversion

??????? byte[] bbuf = bc.getBuffer();

??????? char[] cbuf = cc.getBuffer();

??????? int start = bc.getStart();

??????? for (int i = 0; i < length; i++) {

??????????? cbuf[i] = (char) (bbuf[i + start] & 0xff);

??????? }

??????? uri.setChars(cbuf, 0, length);

?}

從上面的代碼中可以知道對(duì) URL 的 URI 部分進(jìn)行解碼的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定義的,如果沒(méi)有定義,那么將以默認(rèn)編碼 ISO-8859-1 解析。所以如果有中文 URL 時(shí)最好把 URIEncoding 設(shè)置成 UTF-8 編碼。

QueryString 又如何解析? GET 方式 HTTP 請(qǐng)求的 QueryString 與 POST 方式 HTTP 請(qǐng)求的表單參數(shù)都是作為 Parameters 保存,都是通過(guò) request.getParameter 獲取參數(shù)值。對(duì)它們的解碼是在 request.getParameter 方法第一次被調(diào)用時(shí)進(jìn)行的。request.getParameter 方法被調(diào)用時(shí)將會(huì)調(diào)用 org.apache.catalina.connector.Request 的 parseParameters 方法。這個(gè)方法將會(huì)對(duì) GET 和 POST 方式傳遞的參數(shù)進(jìn)行解碼,但是它們的解碼字符集有可能不一樣。POST 表單的解碼將在后面介紹,QueryString 的解碼字符集是在哪定義的呢?它本身是通過(guò) HTTP 的 Header 傳到服務(wù)端的,并且也在 URL 中,是否和 URI 的解碼字符集一樣呢?從前面瀏覽器對(duì) PathInfo 和 QueryString 的編碼采取不同的編碼格式不同可以猜測(cè)到解碼字符集肯定也不會(huì)是一致的。的確是這樣 QueryString 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認(rèn)的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設(shè)置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 設(shè)置為 true。這個(gè)配置項(xiàng)的名字有點(diǎn)讓人產(chǎn)生混淆,它并不是對(duì)整個(gè) URI 都采用 BodyEncoding 進(jìn)行解碼而僅僅是對(duì) QueryString 使用 BodyEncoding 解碼,這一點(diǎn)還要特別注意。

從上面的 URL 編碼和解碼過(guò)程來(lái)看,比較復(fù)雜,而且編碼和解碼并不是我們?cè)趹?yīng)用程序中能完全控制的,所以在我們的應(yīng)用程序中應(yīng)該盡量避免在 URL 中使用非 ASCII 字符,不然很可能會(huì)碰到亂碼問(wèn)題,當(dāng)然在我們的服務(wù)器端最好設(shè)置 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 兩個(gè)參數(shù)。

HTTP Header 的編解碼

當(dāng)客戶(hù)端發(fā)起一個(gè) HTTP 請(qǐng)求除了上面的 URL 外還可能會(huì)在 Header 中傳遞其它參數(shù)如 Cookie、redirectPath 等,這些用戶(hù)設(shè)置的值很可能也會(huì)存在編碼問(wèn)題,Tomcat 對(duì)它們又是怎么解碼的呢?

對(duì) Header 中的項(xiàng)進(jìn)行解碼也是在調(diào)用 request.getHeader 是進(jìn)行的,如果請(qǐng)求的 Header 項(xiàng)沒(méi)有解碼則調(diào)用 MessageBytes 的 toString 方法,這個(gè)方法將從 byte 到 char 的轉(zhuǎn)化使用的默認(rèn)編碼也是 ISO-8859-1,而我們也不能設(shè)置 Header 的其它解碼格式,所以如果你設(shè)置 Header 中有非 ASCII 字符解碼肯定會(huì)有亂碼。

我們?cè)谔砑?Header 時(shí)也是同樣的道理,不要在 Header 中傳遞非 ASCII 字符,如果一定要傳遞的話(huà),我們可以先將這些字符用 org.apache.catalina.util.URLEncoder 編碼然后再添加到 Header 中,這樣在瀏覽器到服務(wù)器的傳遞過(guò)程中就不會(huì)丟失信息了,如果我們要訪(fǎng)問(wèn)這些項(xiàng)時(shí)再按照相應(yīng)的字符集解碼就好了。

POST 表單的編解碼

在前面提到了 POST 表單提交的參數(shù)的解碼是在第一次調(diào)用 request.getParameter 發(fā)生的,POST 表單參數(shù)傳遞方式與 QueryString 不同,它是通過(guò) HTTP 的 BODY 傳遞到服務(wù)端的。當(dāng)我們?cè)陧?yè)面上點(diǎn)擊 submit 按鈕時(shí)瀏覽器首先將根據(jù) ContentType 的 Charset 編碼格式對(duì)表單填的參數(shù)進(jìn)行編碼然后提交到服務(wù)器端,在服務(wù)器端同樣也是用 ContentType 中字符集進(jìn)行解碼。所以通過(guò) POST 表單提交的參數(shù)一般不會(huì)出現(xiàn)問(wèn)題,而且這個(gè)字符集編碼是我們自己設(shè)置的,可以通過(guò) request.setCharacterEncoding(charset) 來(lái)設(shè)置。

另外針對(duì) multipart/form-data 類(lèi)型的參數(shù),也就是上傳的文件編碼同樣也是使用 ContentType 定義的字符集編碼,值得注意的地方是上傳文件是用字節(jié)流的方式傳輸?shù)椒?wù)器的本地臨時(shí)目錄,這個(gè)過(guò)程并沒(méi)有涉及到字符編碼,而真正編碼是在將文件內(nèi)容添加到 parameters 中,如果用這個(gè)編碼不能編碼時(shí)將會(huì)用默認(rèn)編碼 ISO-8859-1 來(lái)編碼。

HTTP BODY 的編解碼

當(dāng)用戶(hù)請(qǐng)求的資源已經(jīng)成功獲取后,這些內(nèi)容將通過(guò) Response 返回給客戶(hù)端瀏覽器,這個(gè)過(guò)程先要經(jīng)過(guò)編碼再到瀏覽器進(jìn)行解碼。這個(gè)過(guò)程的編解碼字符集可以通過(guò) response.setCharacterEncoding 來(lái)設(shè)置,它將會(huì)覆蓋 request.getCharacterEncoding 的值,并且通過(guò) Header 的 Content-Type 返回客戶(hù)端,瀏覽器接受到返回的 socket 流時(shí)將通過(guò) Content-Type 的 charset 來(lái)解碼,如果返回的 HTTP Header 中 Content-Type 沒(méi)有設(shè)置 charset,那么瀏覽器將根據(jù) Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 來(lái)解碼。如果也沒(méi)有定義的話(huà),那么瀏覽器將使用默認(rèn)的編碼來(lái)解碼。

其它需要編碼的地方

除了 URL 和參數(shù)編碼問(wèn)題外,在服務(wù)端還有很多地方可能存在編碼,如可能需要讀取 xml、velocity 模版引擎、JSP 或者從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)等。

xml 文件可以通過(guò)設(shè)置頭來(lái)制定編碼格式

?<?xml version="1.0" encoding="UTF-8"?>

Velocity 模版設(shè)置編碼格式:

?services.VelocityService.input.encoding=UTF-8

JSP 設(shè)置編碼格式:

?<%@page contentType="text/html; charset=UTF-8"%>

訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)都是通過(guò)客戶(hù)端 JDBC 驅(qū)動(dòng)來(lái)完成,用 JDBC 來(lái)存取數(shù)據(jù)要和數(shù)據(jù)的內(nèi)置編碼保持一致,可以通過(guò)設(shè)置 JDBC URL 來(lái)制定如 MySQL:url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"。?

回頁(yè)首

常見(jiàn)問(wèn)題分析

在了解了 Java Web 中可能需要編碼的地方后,下面看一下,當(dāng)我們碰到一些亂碼時(shí),應(yīng)該怎么處理這些問(wèn)題?出現(xiàn)亂碼問(wèn)題唯一的原因都是在 char 到 byte 或 byte 到 char 轉(zhuǎn)換中編碼和解碼的字符集不一致導(dǎo)致的,由于往往一次操作涉及到多次編解碼,所以出現(xiàn)亂碼時(shí)很難查找到底是哪個(gè)環(huán)節(jié)出現(xiàn)了問(wèn)題,下面就幾種常見(jiàn)的現(xiàn)象進(jìn)行分析。

中文變成了看不懂的字符

例如,字符串“淘!我喜歡!”變成了“ì ? £ ?? ò ?2?? £ ?”編碼過(guò)程如下圖所示

?

字符串在解碼時(shí)所用的字符集與編碼字符集不一致導(dǎo)致漢字變成了看不懂的亂碼,而且是一個(gè)漢字字符變成兩個(gè)亂碼字符。

一個(gè)漢字變成一個(gè)問(wèn)號(hào)

例如,字符串“淘!我喜歡!”變成了“??????”編碼過(guò)程如下圖所示

?

將中文和中文符號(hào)經(jīng)過(guò)不支持中文的 ISO-8859-1 編碼后,所有字符變成了,這是因?yàn)橛?/strong> ISO-8859-1 進(jìn)行編解碼時(shí)遇到不在碼值范圍內(nèi)的字符時(shí)統(tǒng)一用 3f 表示,這也就是通常所說(shuō)的黑洞,所有 ISO-8859-1 不認(rèn)識(shí)的字符都變成了

一個(gè)漢字變成兩個(gè)問(wèn)號(hào)

例如,字符串“淘!我喜歡!”變成了“????????????”編碼過(guò)程如下圖所示

?

這種情況比較復(fù)雜,中文經(jīng)過(guò)多次編碼,但是其中有一次編碼或者解碼不對(duì)仍然會(huì)出現(xiàn)中文字符變成現(xiàn)象,出現(xiàn)這種情況要仔細(xì)查看中間的編碼環(huán)節(jié),找出出現(xiàn)編碼錯(cuò)誤的地方。

一種不正常的正確編碼

還有一種情況是在我們通過(guò) request.getParameter 獲取參數(shù)值時(shí),當(dāng)我們直接調(diào)用

?String value = request.getParameter(name);

會(huì)出現(xiàn)亂碼,但是如果用下面的方式

?String value = String(request.getParameter(name).getBytes("

?ISO-8859-1"), "GBK");?

解析時(shí)取得的 value 會(huì)是正確的漢字字符,這種情況是怎么造成的呢?

看下如所示:

?

這種情況是這樣的,ISO-8859-1 字符集的編碼范圍是 0000-00FF,正好和一個(gè)字節(jié)的編碼范圍相對(duì)應(yīng)。這種特性保證了使用 ISO-8859-1 進(jìn)行編碼和解碼可以保持編碼數(shù)值不變。雖然中文字符在經(jīng)過(guò)網(wǎng)絡(luò)傳輸時(shí),被錯(cuò)誤地成了兩個(gè)歐洲字符,但由于輸出時(shí)也是用 ISO-8859-1,結(jié)果被開(kāi)的中文字的兩半又被合并在一起,從而又剛好組成了一個(gè)正確的漢字。雖然最終能取得正確的漢字,但是還是不建議用這種不正常的方式取得參數(shù)值,因?yàn)檫@中間增加了一次額外的編碼與解碼,這種情況出現(xiàn)亂碼時(shí)因?yàn)?/strong> Tomcat 的配置文件中 useBodyEncodingForURI 配置項(xiàng)沒(méi)有設(shè)置為”true”,從而造成第一次解析式用 ISO-8859-1 來(lái)解析才造成亂碼的。

?

轉(zhuǎn)載于:https://www.cnblogs.com/lodor/p/6611097.html

總結(jié)

以上是生活随笔為你收集整理的中文乱码深入分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。