亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

深入分析 Java Web 中的中文編碼問題

jsyzchen / 722人閱讀

摘要:文章首發(fā)地址深入分析中的中文編碼問題背景編碼問題一直困擾著程序開發(fā)人員,尤其是在中更加明顯,因?yàn)槭强缙脚_(tái)的語(yǔ)言,在不同平臺(tái)的編碼之間的切換較多。

文章首發(fā)地址:深入分析 Java Web 中的中文編碼問題

背景:

編碼問題一直困擾著程序開發(fā)人員,尤其是在 Java 中更加明顯,因?yàn)?Java 是跨平臺(tái)的語(yǔ)言,在不同平臺(tái)的編碼之間的切換較多。接下來將介紹 Java 編碼問題出現(xiàn)的根本原因;在 Java 中經(jīng)常遇到的幾種編碼格式的區(qū)別;在 Java 中經(jīng)常需要編碼的場(chǎng)景;出現(xiàn)中文問題的原因分析;在開發(fā) Java Web 中可能存在編碼的幾個(gè)地方;一個(gè) HTTP 請(qǐng)求怎么控制編碼格式;如何避免出現(xiàn)中文編碼問題等。

1、幾種常見的編碼格式 1.1 為什么要編碼

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

要表示的符號(hào)太多,無法用 1 個(gè)字節(jié)來完全表示。

1.2 如何翻譯

計(jì)算機(jī)中提供多種翻譯方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16等。這些都規(guī)定了轉(zhuǎn)化的規(guī)則,按照這個(gè)規(guī)則就可以讓計(jì)算機(jī)正確的表示我們的字符。下面介紹這幾種編碼格式:

ASCII 碼

總共有 128 個(gè),用 1 個(gè)字節(jié)的低 7 位表示, 0 ~ 31 是控制字符如換行、回車、刪除等,32 ~ 126 是打印字符,可以通過鍵盤輸入并且能夠顯示出來。

ISO-8859-1

128 個(gè)字符顯然是不夠用的,所以 ISO 組織在 ASCII 的基礎(chǔ)上擴(kuò)展,他們是 ISO-8859-1 至 ISO-8859-15,前者涵蓋大多數(shù)字符,應(yīng)用最廣。ISO-8859-1 仍是單字節(jié)編碼,它總歸能表示 256 個(gè)字符。

GB2312

它是雙字節(jié)編碼,總的編碼范圍是 A1 ~ F7,其中 A1 ~ A9 是符號(hào)區(qū),總共包含 682 個(gè)符號(hào);B0 ~ F7 是漢字區(qū),包含 6763 個(gè)漢字。

GBk

GBK 為《漢字內(nèi)碼擴(kuò)展規(guī)范》,為 GB2312 的擴(kuò)展,它的編碼范圍是 8140 ~ FEFE(去掉XX7F),總共有 23940 個(gè)碼位,能表示 21003 個(gè)漢字,和 GB2312的編碼兼容,不會(huì)有亂碼。

UTF-16

它具體定義了 Unicode 字符在計(jì)算機(jī)中的存取方法。UTF-16 用兩個(gè)字節(jié)來表示 Unicode 的轉(zhuǎn)化格式,它采用定長(zhǎng)的表示方法,即不論什么字符用兩個(gè)字節(jié)表示。兩個(gè)字節(jié)是 16 個(gè) bit,所以叫 UTF-16。它表示字符非常方便,沒兩個(gè)字節(jié)表示一個(gè)字符,這就大大簡(jiǎn)化了字符串操作。

UTF-8

雖說 UTF-16 統(tǒng)一采用兩個(gè)字節(jié)表示一個(gè)字符很簡(jiǎn)單方便,但是很大一部分字符用一個(gè)字節(jié)就可以表示,如果用兩個(gè)字節(jié)表示,存儲(chǔ)空間放大了一倍,在網(wǎng)絡(luò)帶寬有限的情況下會(huì)增加網(wǎng)絡(luò)傳輸?shù)牧髁?。UTF-8 采用了一種變長(zhǎng)技術(shù),每個(gè)編碼區(qū)域有不同的字碼長(zhǎng)度不同類型的字符可以由 1 ~ 6 個(gè)字節(jié)組成。

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

如果是 1 個(gè)字節(jié),最高位(第 8 位)為 0,則表示這是一個(gè) ASCII 字符(00 ~ 7F)

如果是 1 個(gè)字節(jié),以 11 開頭,則連續(xù)的 1 的個(gè)數(shù)暗示這個(gè)字符的字節(jié)數(shù)

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

?

2、在 Java 中需要編碼的場(chǎng)景 2.1 在 I/O 操作中存在的編碼

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

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

 String file = "c:/stream.txt"; 
 String charset = "UTF-8"; 
 // 寫字符換轉(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)亂碼問題。

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

在內(nèi)存中進(jìn)行從字符到字節(jié)的數(shù)據(jù)類型轉(zhuǎn)換。

1、String 類提供字符串轉(zhuǎn)換到字節(jié)的方法,也支持將字節(jié)轉(zhuǎn)換成字符串的構(gòu)造函數(shù)。

String s  = "字符串";
byte[] b = s.getBytes("UTF-8");
String n = new String(b, "UTF-8");

2、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);

...

3、在 Java 中如何編解碼

Java 編碼類圖

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

Java 編碼時(shí)序圖

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

經(jīng)常會(huì)出現(xiàn)中文變成“?”很可能就是錯(cuò)誤的使用了 ISO-8859-1 這個(gè)編碼導(dǎo)致的。中文字符經(jīng)過 ISO-8859-1 編碼會(huì)丟失信息,通常我們稱之為“黑洞”,它會(huì)把不認(rèn)識(shí)的字符吸收掉。由于現(xiàn)在大部分基礎(chǔ)的 Java 框架或系統(tǒng)默認(rèn)的字符集編碼都是 ISO-8859-1,所以很容易出現(xiàn)亂碼問題,后面將會(huì)分析不同的亂碼形式是怎么出現(xiàn)的。

幾種編碼格式的比較

對(duì)中文字符后面四種編碼格式都能處理,GB2312 與 GBK 編碼規(guī)則類似,但是 GBK 范圍更大,它能處理所有漢字字符,所以 GB2312 與 GBK 比較應(yīng)該選擇 GBK。UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規(guī)則不太相同,相對(duì)來說 UTF-16 編碼效率最高,字符到字節(jié)相互轉(zhuǎn)換更簡(jiǎn)單,進(jì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 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

4、在 Java Web 中涉及的編解碼

對(duì)于使用中文來說,有 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)過網(wǎng)絡(luò)傳輸都是以字節(jié)為單位的,所以所有的數(shù)據(jù)都必須能夠被序列化為字節(jié)。在 Java 中數(shù)據(jù)被序列化必須繼承 Serializable 接口。

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

我們能夠看到的漢字都是以字符形式出現(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é),所以兩個(gè)漢字用 char 表示在內(nèi)存中占用相當(dāng)于四個(gè)字節(jié)的空間。

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

用戶從瀏覽器端發(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ù)都可能存在編碼問題,當(dāng) Servlet 處理完所有請(qǐng)求的數(shù)據(jù)后,需要將這些數(shù)據(jù)再編碼通過 Socket 發(fā)送到用戶請(qǐng)求的瀏覽器里,再經(jīng)過瀏覽器解碼成為文本。這些過程如下圖所示:

一次 HTTP 請(qǐng)求的編碼示例

4.1 URL 的編解碼

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

上圖中以 Tomcat 作為 Servlet Engine 為例,它們分別對(duì)應(yīng)到下面這些配置文件中:
Port 對(duì)應(yīng)在 Tomcat 的 中配置,而 Context Path 在 中配置,Servlet Path 在 Web 應(yīng)用的 web.xml 中的

 
        junshanExample 
        /servlets/servlet/* 
 

中配置,PathInfo 是我們請(qǐng)求的具體的 Servlet,QueryString 是要傳遞的參數(shù),注意這里是在瀏覽器里直接輸入 URL 所以是通過 Get 方法請(qǐng)求的,如果是 POST 方法請(qǐng)求的話,QueryString 將通過表單方式提交到服務(wù)器端。

上圖中 PathInfo 和 QueryString 出現(xiàn)了中文,當(dāng)我們?cè)跒g覽器中直接輸入這個(gè) URL 時(shí),在瀏覽器端和服務(wù)端會(huì)如何編碼和解析這個(gè) URL 呢?為了驗(yàn)證瀏覽器是怎么編碼 URL 的我選擇的是360極速瀏覽器并通過 Postman 插件觀察我們請(qǐng)求的 URL 的實(shí)際的內(nèi)容,以下是 URL:

HTTP://localhost:8080/examples/servlets/servlet/君山?author=君山

君山的編碼結(jié)果是:e5 90 9b e5 b1 b1,和《深入分析 Java Web 技術(shù)內(nèi)幕》中的結(jié)果不一樣,這是因?yàn)槲沂褂玫臑g覽器和插件和原作者是有區(qū)別的,那么這些瀏覽器之間的默認(rèn)編碼是不一樣的,原文中的結(jié)果是:

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

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

解析請(qǐng)求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個(gè)方法把傳過來的 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 的 中定義的,如果沒有定義,那么將以默認(rèn)編碼 ISO-8859-1 解析。所以如果有中文 URL 時(shí)最好把 URIEncoding 設(shè)置成 UTF-8 編碼。

QueryString 又如何解析? GET 方式 HTTP 請(qǐng)求的 QueryString 與 POST 方式 HTTP 請(qǐng)求的表單參數(shù)都是作為 Parameters 保存,都是通過 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 的解碼字符集是在哪定義的呢?它本身是通過 HTTP 的 Header 傳到服務(wù)端的,并且也在 URL 中,是否和 URI 的解碼字符集一樣呢?從前面瀏覽器對(duì) PathInfo 和 QueryString 的編碼采取不同的編碼格式不同可以猜測(cè)到解碼字符集肯定也不會(huì)是一致的。的確是這樣 QueryString 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認(rèn)的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設(shè)置 connector 的 中的 useBodyEncodingForURI 設(shè)置為 true。這個(gè)配置項(xiàng)的名字有點(diǎn)讓人產(chǎn)生混淆,它并不是對(duì)整個(gè) URI 都采用 BodyEncoding 進(jìn)行解碼而僅僅是對(duì) QueryString 使用 BodyEncoding 解碼,這一點(diǎn)還要特別注意。

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

4.2 HTTP Header 的編解碼

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

對(duì) Header 中的項(xiàng)進(jìn)行解碼也是在調(diào)用 request.getHeader 是進(jìn)行的,如果請(qǐng)求的 Header 項(xiàng)沒有解碼則調(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 字符,如果一定要傳遞的話,我們可以先將這些字符用 org.apache.catalina.util.URLEncoder 編碼然后再添加到 Header 中,這樣在瀏覽器到服務(wù)器的傳遞過程中就不會(huì)丟失信息了,如果我們要訪問這些項(xiàng)時(shí)再按照相應(yīng)的字符集解碼就好了。

4.3 POST 表單的編解碼

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

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

4.4 HTTP BODY 的編解碼

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

4.5 其它需要編碼的地方

除了 URL 和參數(shù)編碼問題外,在服務(wù)端還有很多地方可能存在編碼,如可能需要讀取 xml、velocity 模版引擎、JSP 或者從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)等。
xml 文件可以通過設(shè)置頭來制定編碼格式

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

services.VelocityService.input.encoding=UTF-8

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

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

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

5、常見問題分析

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

5.1 中文變成了看不懂的字符

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

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

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

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

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

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

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

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

5.4 一種不正常的正確編碼

還有一種情況是在我們通過 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)過網(wǎng)絡(luò)傳輸時(shí),被錯(cuò)誤地“拆”成了兩個(gè)歐洲字符,但由于輸出時(shí)也是用 ISO-8859-1,結(jié)果被“拆”開的中文字的兩半又被合并在一起,從而又剛好組成了一個(gè)正確的漢字。雖然最終能取得正確的漢字,但是還是不建議用這種不正常的方式取得參數(shù)值,因?yàn)檫@中間增加了一次額外的編碼與解碼,這種情況出現(xiàn)亂碼時(shí)因?yàn)?Tomcat 的配置文件中 useBodyEncodingForURI 配置項(xiàng)沒有設(shè)置為”true”,從而造成第一次解析式用 ISO-8859-1 來解析才造成亂碼的。

6、總結(jié)

本文首先總結(jié)了幾種常見編碼格式的區(qū)別,然后介紹了支持中文的幾種編碼格式,并比較了它們的使用場(chǎng)景。接著介紹了 Java 那些地方會(huì)涉及到編碼問題,已經(jīng) Java 中如何對(duì)編碼的支持。并以網(wǎng)絡(luò) I/O 為例重點(diǎn)介紹了 HTTP 請(qǐng)求中的存在編碼的地方,以及 Tomcat 對(duì) HTTP 協(xié)議的解析,最后分析了我們平常遇到的亂碼問題出現(xiàn)的原因。

綜上所述,要解決中文問題,首先要搞清楚哪些地方會(huì)引起字符到字節(jié)的編碼以及字節(jié)到字符的解碼,最常見的地方就是讀取會(huì)存儲(chǔ)數(shù)據(jù)到磁盤,或者數(shù)據(jù)要經(jīng)過網(wǎng)絡(luò)傳輸。然后針對(duì)這些地方搞清楚操作這些數(shù)據(jù)的框架的或系統(tǒng)是如何控制編碼的,正確設(shè)置編碼格式,避免使用軟件默認(rèn)的或者是操作系統(tǒng)平臺(tái)默認(rèn)的編碼格式。

注明:文章大部分參考書籍《深入 Java Web 技術(shù)內(nèi)幕》第三章,自己有刪減,二次轉(zhuǎn)載請(qǐng)也務(wù)必注明此出處。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/69857.html

相關(guān)文章

  • Java后端

    摘要:,面向切面編程,中最主要的是用于事務(wù)方面的使用。目標(biāo)達(dá)成后還會(huì)有去構(gòu)建微服務(wù),希望大家多多支持。原文地址手把手教程優(yōu)雅的應(yīng)用四手把手實(shí)現(xiàn)后端搭建第四期 SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Spring 兩大核心之 AOP 學(xué)習(xí) | 掘金技術(shù)征文 原本地址:SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Sp...

    joyvw 評(píng)論0 收藏0
  • 前端資源系列(4)-前端學(xué)習(xí)資源分享&前端面試資源匯總

    摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...

    princekin 評(píng)論0 收藏0
  • 寫這么多系列博客,怪不得找不到女朋友

    摘要:前提好幾周沒更新博客了,對(duì)不斷支持我博客的童鞋們說聲抱歉了。熟悉我的人都知道我寫博客的時(shí)間比較早,而且堅(jiān)持的時(shí)間也比較久,一直到現(xiàn)在也是一直保持著更新狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好幾周沒更新博客了,對(duì)不斷支持我博客的童鞋們說聲:抱歉了!。自己這段時(shí)...

    JerryWangSAP 評(píng)論0 收藏0
  • Java開發(fā)

    摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...

    LuDongWei 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<