Java web開發過程常常遇到亂碼,本篇咱們探討一下亂碼產生的緣由與解決思路。javascript
一次完整的Web請求會有4次編解碼轉換,以下所示。html
第一次:客戶端(一般爲瀏覽器)將字符轉換成TCP字節流發向服務器。java
這裏有一次字符到字節的轉換。程序員
第二次:服務器讀取客戶端發來的TCP字節流,轉換成字符串。web
這裏是一次字節到字符的轉換。chrome
第三次:服務器將結果字符串換成TCP字節流發向客戶端。瀏覽器
這裏又有一次字符到字節的轉換。服務器
第四次:客戶端讀取服務端發過來的響應字節流。轉換成字符串顯示。tcp
一個完整的Web請求就結束了。工具
聰明的你已經發現了,第一次轉換和第二次轉換是一對對應的編解碼。第三次和第四次轉換是一對對應的編解碼。也就是第一次會哪一種字符集編碼,第二次就要用相同的字符集解碼。
第三次能夠選擇與前兩次不一樣的字符集,但第四次必須第三次相同。不錯,你已經入門了。
咱們怎麼找到第一次的編碼的字符。Web客戶端程序的做者定然知道他用什麼字符發送Web請求,咱們就很少說了。咱們這裏只說瀏覽器,由於絕大數請求是瀏覽器發出來的。
瀏覽器在提交post或get表單時,採用是瀏覽器當前頁面的編碼。
查看chrome瀏覽器、360極速瀏覽器等當前頁面編碼:點擊瀏覽器右側菜單圖標,而後依次將鼠標移到「工具」→「編碼」便可查看或更改當前頁面的編碼模式。
而當前頁的編碼是當瀏覽器獲取該頁面時,第四次轉換決定的。瀏覽器是根據響應頭和響應正文決定採用哪一種編碼。
發現沒有,上面咱們說到第一次轉換決定了第二次轉換的編碼,第三次決定了第四次轉換的編碼。而這裏第四次又決定第一次轉換的編碼。一個環形轉換造成了。
A1=A2, A3=A4, A4=A1因此A1=A2=A3=A4. 證實選擇相同的字符是完成正確編碼的轉換的充分條件。
說完了第一次編碼,咱們講一下第二次解碼。
客戶端發過的請求報文,分三部分: 請求行,請求頭,POST正文。存在亂碼可能的位置有兩個地方,請求URL的參數部分和POST正文。(英文字符爲何沒亂碼? 由於採用ASCII碼,絕大部分字符集對英文的編碼都同樣的)。
服務器在解析這兩部分時,分別有自已的字符集。拿Tomcat來講,urlEncoding參數指定解析URL參數部分的編碼。而request.getCharsetEncoding()指定的是解析POST正文的字符集。
說完第二次解碼,說一下第三次的編碼。
服務端將字符發向客戶端,必須轉換成字節流。用什麼編碼好呢? JSP頁面有兩個設置選項:pageEncoding和contentType。你注意到了嗎?
通常狀況他們都會同時出現。pageEncoding表示是JSP文件的編碼,而contentType是服務端將字符發向客戶端的字符集編碼。這個字符集會寫在響應報文頭的Content-Type字段中的。Content-Type:"text/javascript;charset=gbk"。只有contentType存在,好理解。pageEncoding的出現,與contentType有了點情感糾葛。記得是一點。你們知道JSP文件須要編譯成Java文件.
這個過程:1. 讀取JSP文件,2. 轉換Java類字符串,3.寫入JAVA文件。文件都是字節流。讀取JSP文件就是採用pageEncoding字符集來解碼。寫入JAVA的編碼一概爲UTF-8(由於JAVAC用UTF-8去編譯java到class).
這一點情感糾葛就在,contentType不存在時候,pageEncoding字符集會代替他。
下面第四次是在瀏覽器顯示最終結果的時候。
瀏覽器採用響應頭中的Content-Type字段來解析響應報頭,並顯示。
若是Content-Type不存在,則瀏覽器會採用:
<meta http-equiv=Content-Type content="text/html;charset=gb2312">
指定的字符集來解碼。
完了,聽上去好像很簡哦,但爲何會出現那麼多亂碼的狀況?常見狀況:
AJAX請求。
AJAX請求的編碼是程序設定的。不在A1到A4這環形家庭中。程序
員沒能理解各個編碼參數做用點,因此出錯,由上面4步的分析。若是還不能理解,我只能呵呵。
urlEncoding各平不同。
Post正文是由request.getCharsetEncoding()字符集解析,這個字符集是程序控制的。URL的請求參數是由urlEncoding字符集解析的。而urlEncoding通常由服務器設定不同,好比Tomcat默認爲iso8859-1。遷移過程當中注意這個。
JSP文件保存格式不對。
JSP中pageEncoding指定爲GBK,JSP文件卻保存成UTF-8,轉換就成亂碼了。後續不用看,確定亂。解決亂碼問題必定要先排除這個問題。
編碼不統一。
一個項目幾種編碼,什麼樣的團隊呀。子系統內部能夠,一跨界,完蛋。
若是出現亂碼,怎麼排查?
通常二分法,看看服務端顯示是否是正確的,通常將參數System.out輸出到Console或者日誌(必定要注意日誌文件你打開時的編碼,原本輸出是對的,你反而打開亂了)。
若是能看到正確字符串,通常是第三次或第四次轉換不正確,若是看到亂碼,前二次轉換不正確。第二種狀況爲絕大多數,由於前一種狀況,程序員的參與度很小。
我只說第二種狀況:
首先肯定是URL參數,仍是POST參數亂碼。
根據1獲取urlEncoding或request.getCharsetEncoding.
經過查看瀏覽器,或經過httpwatch或tcpdump等工具來肯定客戶端請求的編碼。在我國基本上GBK,UTF-8。UTF-8基本用3個字節表示中文,GBK用兩處,很好區分。
改爲一致就行了。
2016-12-24夜,蘇州