Java servlet 使用 PrintWriter 時(shí)的編碼與亂碼的示例代碼
在前面的網(wǎng)頁(yè)中的編碼與亂碼系列中,曾多次提到使用 servlet 方式構(gòu)建的動(dòng)態(tài)響應(yīng)流,不過(guò)在那里都是直接使用字節(jié)流的方式,不過(guò),更為常見(jiàn)的方式是使用字符流。而在前面,又談到了 Java 字節(jié)流與字符流的話(huà)題。
有了前面的基礎(chǔ),現(xiàn)在來(lái)說(shuō)下 Java servlet 中使用字符流,也即是 PrintWriter 時(shí)的編碼與亂碼問(wèn)題。
回顧字節(jié)流的情形
先回顧一下,在之前的字節(jié)流響應(yīng)中,我們使用 String.getBytes 方法,然后總是顯式傳入編碼的參數(shù),使它與 meta 中或者 header 的聲明一致。比如這樣:
或者這樣:
只要保持了一致,就不用擔(dān)心發(fā)生亂碼的問(wèn)題。
使用 PrintWriter 字符流,缺省編碼
現(xiàn)在假如使用 PrintWriter 來(lái)作為響應(yīng)呢?比如這樣:
代碼中并沒(méi)有顯式傳入什么編碼的參數(shù),不像 String.getBytes 那樣。另一方面,我們知道,字符流最終還是要轉(zhuǎn)換成字節(jié)流,可是它到底使用了什么編碼呢?是不是 Charset.defaultCharset 中的值呢?
就以上述代碼為例,假如現(xiàn)在在瀏覽器中查看,會(huì)發(fā)現(xiàn)結(jié)果是這樣的:
可見(jiàn) defaultCharset 缺省是 utf-8,前面說(shuō)過(guò),這其實(shí)來(lái)自于啟動(dòng) tomcat server 時(shí)所傳入的參數(shù) –Dfile.encoding:
但漢字卻沒(méi)有正確輸出,可見(jiàn) PrintWriter 并沒(méi)有采用這個(gè)缺省值。查看 header 中的響應(yīng):
也沒(méi)有任何編碼的指示。
雖然 meta 中聲明是 utf-8,輸出的缺省字符集的值也是 utf-8,可是從最終結(jié)果不難看出 PrintWriter 并沒(méi)有采納這個(gè)值來(lái)轉(zhuǎn)換字節(jié)流。(實(shí)際上它根本不會(huì)試圖去理解這個(gè))。
看一看它的文檔說(shuō)明,會(huì)發(fā)現(xiàn)情況有點(diǎn)不一樣:
原來(lái)沒(méi)有指定時(shí),PrintWriter 不是用 Charset.defaultCharset 中的值,而是用 response.getCharacterEncoding 方法中所返回的值,而沒(méi)有指定的話(huà),那個(gè)方法其實(shí)就返回一個(gè)缺省值:ISO-8859-1。
再看看 getCharacterEncoding 方法:
可以看到它的值又是來(lái)源于顯式的 response.setCharacterEncoding 或 response.setContentType 方法,或者是隱式的 setLocale 方法。(顯式的具有更高的優(yōu)先級(jí))假如沒(méi)有,就用缺省的 ISO-8859-1。
它還提到 RFC 2047 標(biāo)準(zhǔn) ,打開(kāi)看看,是關(guān)于 MIME 中非 ASCII 文本的消息頭擴(kuò)展(MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text)的。文中有一處提到如果字符集編碼缺失,推薦用 iso8859 系列:
注意這里沒(méi)有明說(shuō)是 iso-8859-1,它說(shuō)的是 iso-8859-*,不過(guò) servlet 最終采用的是 iso-8859-1.
所以現(xiàn)在清楚了,缺省用 iso-8859-1,可以用 getCharacterEncoding 得到它的值,不過(guò) iso 不支持中文字符,所以響應(yīng)流中不能出現(xiàn)中文:
結(jié)果是這樣:
使用 PrintWriter 字符流,顯式指定編碼
按照前面說(shuō)的,可以在 write 之前使用 setCharacterEncoding 等方法指定編碼:
這樣就 OK 了:
要注意,這種情況下,response header 中仍然沒(méi)有 charset 信息,所以要在 meta 中指定。
也可以用 setContentType (或前面一直用的 setHeader,其實(shí)兩者是等價(jià)的):
也能達(dá)成同樣效果:
這種情況下,response header 中包含 charset 信息,所以前面的代碼中可以省略在 meta 中的聲明:
那么,現(xiàn)在我們明白了,PrintWriter 的缺省與普通字符流的缺省是不同的,機(jī)制有所差別。
使用普通字符流,缺省編碼
當(dāng)然如果你一定要用普通字符流,也是可以的,但最后需要主動(dòng) flush:
這時(shí)的缺省就是 Charset.defaultCharset 中的值了,這里把它拼在了 meta 和最終的輸出中,響應(yīng)也是正常的:
結(jié)果是 utf-8。跟前面所說(shuō)的 tomcat server 啟動(dòng)時(shí)參數(shù)的值一致。
使用普通字符流,顯式指定編碼
如果不打算用缺省,那就直接指定:
結(jié)果同樣是 OK 的:
當(dāng)然,一般還是建議使用 PrintWriter 來(lái)輸出,而即便你一定要用普通字符流,也最好不要用缺省。
那么關(guān)于 Java servlet 中使用 PrintWriter 時(shí)的編碼與亂碼問(wèn)題就介紹到這里。本文中的示例代碼見(jiàn):servlet-PrintWriter_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot+vue實(shí)現(xiàn)頁(yè)面下載文件
這篇文章主要為大家詳細(xì)介紹了springboot+vue實(shí)現(xiàn)頁(yè)面下載文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12Jenkins節(jié)點(diǎn)配置實(shí)現(xiàn)原理及過(guò)程解析
這篇文章主要介紹了Jenkins節(jié)點(diǎn)配置實(shí)現(xiàn)原理及過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09springboot中使用Hibernate-Validation校驗(yàn)參數(shù)詳解
這篇文章主要為大家介紹了springboot中使用Hibernate-Validation校驗(yàn)參數(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07詳解eclipse將項(xiàng)目打包成jar文件的兩種方法及問(wèn)題解決方法
本文給大家介紹了eclipse中將項(xiàng)目打包成jar文件的兩種方法及其遇到問(wèn)題解決方法,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12通過(guò)java.util.TreeMap源碼加強(qiáng)紅黑樹(shù)的理解
通過(guò)分析java.util.TreeMap源碼來(lái)對(duì)經(jīng)典問(wèn)題紅黑樹(shù)加強(qiáng)理解和理清思路。2017-11-11關(guān)于java自定義線(xiàn)程池的原理與實(shí)現(xiàn)
本文介紹了如何自定義線(xiàn)程池和阻塞隊(duì)列,包括阻塞隊(duì)列的實(shí)現(xiàn)方法,線(xiàn)程池的構(gòu)建以及拒絕策略的應(yīng)用,詳細(xì)闡述了線(xiàn)程池中任務(wù)的提交和執(zhí)行流程,以及如何處理任務(wù)超出隊(duì)列容量的情況2022-04-04