使用 servlet 向客戶端瀏覽器回送中文時,經常出現中文亂碼的問題,這里給大家完完全全地搞明白:
一、基本常識
-
中文系統默認是 GBK 編碼(GBK是對GB2312的補充,包含它)
-
需要處理編碼問題的地方:
- 瀏覽器發送請求(Request)時,所用的編碼格式;
- Web 服務器響應(Response)回送的數據,所用的編碼格式;
- 瀏覽器解析響應回送的數據,所用的編碼格式;
- 又分為兩種情況:
- 請求發生亂碼往往是 servlet 程序獲取請求信息時,獲取的信息亂碼,問題產生在服務端;
- 而客戶端瀏覽器出現,往往是2、3兩處的編碼格式不一造成的;
-
查看平台默認字符編碼方法:java.nio.charset.Charset.defaultCharset();
-
HttpServletResponse 類中的 getOutputStream() 獲取的字節節流,用於向瀏覽器輸出二進制數據(圖片、視頻等任何形式數據);而 getWriter() 獲取的是字符輸出流,用於輸出文本數據,二者不能同時使用;
-
getBytes() 方法通過平台默認字符集進行編碼,可傳傳遞參數指定字符集(如 getBytes("UTF-8"))
二、了解編碼的過程
以我們要解決的問題角度出發:編碼是將文字按照一種規范轉變成另一種形式的過程;而解碼,是編碼的逆過程;這中間的依據也就是那個規范,就是字符集(編碼表,如 UTF-8、GBK)。
編碼時,程序從字符集中尋找字符在此字符集里的一個"編號"(往往是一種更利於計算機使用字符);這些編號組成的數據,無論傳遞到什么平台,最后接收數據的平台使用,同一字符集進行解碼,就能獲取到原本的數據,過程:用接收到的編碼數據,從字符集中尋找對應表示的字符進行還原,最終數據的完美傳遞。
三、解決亂碼問題
Request 亂碼
從瀏覽器發起的訪問方式有三種:
- 在地址欄直接輸入URL訪問:瀏覽器默認將請求參數按 UTF-8 進行編碼,
- 點擊超鏈接訪問:瀏覽器將參數按照當前頁面的顯示編碼進行編碼
- 提交表單訪問:同上
網上常用的設置表單的方法:request.setCharacterEncoding("UTF-8")
只對 POST 請求方式有效,所以解決請求亂碼要針對兩個情況:
GET 方式
get請求亂碼解決辦法一:修改服務器端對URI參數的默認編碼
get請求亂碼辦法一:修改服務器端對URI參數的默認編碼
在tomcat的server.xml中,設置元素的屬性URIEncoding=”UTF-8”即可。(默認沒有設置此屬性)
擴展:
1.設置元素的屬性useBodyEncodingForURI=“true”,意思是請求體和uri使用相同的編碼格式。
通過設置這兩個屬性,既可以解決get方式的亂碼,又可以解決post方式的亂碼。
2.通過修改server.xml指定服務器對get和post統一按照utf-8解碼,要求tomcat管理下的所有web應用都要使用utf-8編碼,即所有的jsp、html頁面都必須使用utf-8編碼。
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"
URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
get請求亂碼辦法二:逆向操作
參數從瀏覽器到服務器,經過客戶端編碼,服務器端解碼,最終成為亂碼。那我們將亂碼進行相反的編解碼,即可得到正常的參數值。此時注意上文提到的編碼規律總結,只有源頭對了,才能成功解決問題。
String name = request.getParameter("name");//得到亂碼的數據
name = new String(name.getBytes("GBK"),"utf-8");
//將得到的數據進行GBK方式解碼,然后把得到的字節再通過UTF-8編碼,得到正常的name值
get請求亂碼方法三:手動編碼解碼
在JSP頁面中先把中文編碼后再提交,服務器獲取后再按照同樣的方式解碼
//場景:從網頁下載,文件名為中文
...
String filePath = "D:\\javacode\\javaweb_Servlet2\\response\\src\\main\\webapp\\哈哈11.xml";
// 通過一些方法,從路徑中提取出 文件名:
//通過獲取最后一個 / 的腳標 + 1 確定文件名的第一個字符索引,而后從此索引開始截取字符串,就是文件名
String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
//URLEncoder.encode() 方法設置以指定字符集對文件名進行編碼
resp.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(fileName,"utf-8"));
...
POST 方式
post方式提交的參數存在於請求體中,瀏覽器將參數按照當前頁面的顯示編碼進行編碼頁面的編碼方式一般情況下已經被設置成了UTF-8,只需要修改服務端解碼方式(與瀏覽器頁面編碼方式一致) ```java
request.setCharacterEncoding(“UTF-8”);
Response 亂碼
實際遇到更多的亂碼情況是瀏覽器接收響應數據的亂碼,我們需要了解發生亂碼的流程:
首先 getBytes() 以 UTF-8 字符集對字符串進行編碼,並寫入響應對象,響應數據回送至瀏覽器,瀏覽器默認使用平台默認編碼(中文系統)進行解碼,用 GB 碼表解碼 UTF-8 的編碼的數據,結果就是出現亂碼。
所以我們要做的使:響應所回送數據所用字符集 和 瀏覽器用來解析回送數據所用字符集相同
設置瀏覽器的解碼格式:
response.setHeader("content-type","text/html;charset=UTF-8");
//或
response.setContentType("text/html;charset=utf-8");
大白話說就是用 content-type 響應頭,告訴瀏覽器我回送過來的數據時 text/html 類型,要用 UTF-8 字符集解碼;
設置好瀏覽器用於解析響應回送數據的字符集后,現在要設置響應回送的數據字符集,分為兩種情況,也就是最開始提到的,處理字節和字符流數據是有差異的:
- 使用 response.getWriter() 流寫出的的數據亂碼解決方式:
//設置將發送到客戶端的響應 的字符編碼,只能用於設置 getWritet()的字符編碼
response.setCharactEncoding("UTF-8");
//同時設置瀏覽器的解碼方式
response.setHeader("content-type","text/html;charset=UTF-8");
需要在調用 getWriter() 方法前 和 響應提交前使用
- 使用 response.getOutputStream() 流寫出的數據亂碼解決方式:
//服務端對字符進行編碼的時候,指定編碼方式
response.getOutputStream().write("漢字".getBytes("UTF-8"));
//同時設置瀏覽器的解碼方式
response.setHeader("content-type","text/html;charset=UTF-8");
字符響應流只用用來輸出字符,而字節響應流可以用來輸出任何東西
四、擴展:
- 查看當前響應頭所設置的字符編碼(沒有獲取到字符集類型說明是默認的):response.getContentType()
- 可使用 response.writer().print() 傳遞 HTML 標簽代碼間接設置網頁的編碼格式,如:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//直接向網頁輸出 HTML 標簽代碼
response.getWriter().println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>");
}
- 為了避免可能的失誤,直接將設置編碼相關代碼放在最前面(無論是后端還是前端)