Java servlet 使用 PrintWriter 時的編碼與亂碼 轉

在前面的網頁中的編碼與亂碼系列中(),曾多次提到使用 servlet 方式構建的動態響應流,不過在那裏都是直接使用字節流的方式,不過,更爲常見的方式是使用字符流。而在前面,又談到了 Java 字節流與字符流的話題()。

有了前面的基礎,現在來說下 Java servlet 中使用字符流,也即是 PrintWriter 時的編碼與亂碼問題。

回顧字節流的情形

先回顧一下,在之前的字節流響應中,我們使用 String.getBytes 方法,然後總是顯式傳入編碼的參數,使它與 meta 中或者 header 的聲明一致。比如這樣:

或者這樣:

只要保持了一致,就不用擔心發生亂碼的問題。

使用 PrintWriter 字符流,缺省編碼

現在假如使用 PrintWriter 來作爲響應呢?比如這樣:

printwriter default vs charset.defaultcharset 代碼

代碼中並沒有顯式傳入什麼編碼的參數,不像 String.getBytes 那樣。另一方面,我們知道,字符流最終還是要轉換成字節流,可是它到底使用了什麼編碼呢?是不是 Charset.defaultCharset 中的值呢?

就以上述代碼爲例,假如現在在瀏覽器中查看,會發現結果是這樣的:

printwriter default vs charset.defaultcharset 響應

可見 defaultCharset 缺省是 utf-8,前面說過,這其實來自於啓動 tomcat server 時所傳入的參數 –Dfile.encoding,(見前面篇章 Java 字節流與字符流(3)):

但漢字卻沒有正確輸出,可見 PrintWriter 並沒有採用這個缺省值。查看 header 中的響應:

printwriter default header

也沒有任何編碼的指示。

雖然 meta 中聲明是 utf-8,輸出的缺省字符集的值也是 utf-8,可是從最終結果不難看出 PrintWriter 並沒有採納這個值來轉換字節流。(實際上它根本不會試圖去理解這個)。

看一看它的文檔說明,會發現情況有點不一樣:

response.getWriter javadoc

原來沒有指定時,PrintWriter 不是用 Charset.defaultCharset 中的值,而是用 response.getCharacterEncoding 方法中所返回的值,而沒有指定的話,那個方法其實就返回一個缺省值:ISO-8859-1。

再看看 getCharacterEncoding 方法:

response.getCharacterEncoding javadoc

可以看到它的值又是來源於顯式的 response.setCharacterEncoding 或 response.setContentType 方法,或者是隱式的 setLocale 方法。(顯式的具有更高的優先級)假如沒有,就用缺省的 ISO-8859-1。

它還提到 RFC 2047 標準,打開看看,是關於 MIME 中非 ASCII 文本的消息頭擴展(MIME (Multipurpose Internet Mail Extensions) Part Three:  Message Header Extensions for Non-ASCII Text)的。文中有一處提到如果字符集編碼缺失,推薦用 iso8859 系列:

rfc2047 recommend character set

注意這裏沒有明說是 iso-8859-1,它說的是 iso-8859-*,不過 servlet 最終採用的是 iso-8859-1.

所以現在清楚了,缺省用 iso-8859-1,可以用 getCharacterEncoding 得到它的值,不過 iso 不支持中文字符,所以響應流中不能出現中文:

printwriter 字符流 iso-8859-1 代碼

結果是這樣:

printwriter 字符流 iso-8859-1 響應

使用 PrintWriter 字符流,顯式指定編碼

按照前面說的,可以在 write 之前使用 setCharacterEncoding 等方法指定編碼:

printwriter 字符流 utf-8 代碼

這樣就 OK 了:

printwriter 字符流 utf-8 響應

要注意,這種情況下,response header 中仍然沒有 charset 信息,所以要在 meta 中指定。

也可以用 setContentType (或前面一直用的 setHeader,其實兩者是等價的):

printwriter 字符流 gbk 代碼

也能達成同樣效果:

printwriter 字符流 gbk 響應

這種情況下,response header 中包含 charset 信息,所以前面的代碼中可以省略在 meta 中的聲明:

printwriter header content-type gbk

那麼,現在我們明白了,PrintWriter 的缺省與普通字符流的缺省是不同的,機制有所差別。

使用普通字符流,缺省編碼

當然如果你一定要用普通字符流,也是可以的,但最後需要主動 flush:

普通字符流 utf-8 代碼

這時的缺省就是 Charset.defaultCharset 中的值了,這裏把它拼在了 meta 和最終的輸出中,響應也是正常的:

普通字符流 utf-8 響應

結果是 utf-8。跟前面所說的 tomcat server 啓動時參數的值一致。

使用普通字符流,顯式指定編碼

如果不打算用缺省,那就直接指定:

普通字符流 gbk 代碼

結果同樣是 OK 的:

普通字符流 gbk 響應

當然,一般還是建議使用 PrintWriter 來輸出,而即便你一定要用普通字符流,也最好不要用缺省。

那麼關於 Java servlet 中使用 PrintWriter 時的編碼與亂碼問題就介紹到這裏。本文中的示例代碼見: https://gitee.com/goldenshaw/java_code_complete/tree/0c0993f6ce8972d28e612a18a40d4474772fd884/jcc-web/src/main/java/org/jcc/servlet/encoding/page/writer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章