JAVA WEB 亂碼問題解析 亂碼原因 在Java Web開發過程中,經常遇到亂碼的問題,造成亂碼的原因,概括起來就是對字元編碼和解碼的方式不匹配。 既然亂碼的原因是字元編碼與解碼的方式不匹配,那麼為什麼我們一定要對字元進行編碼,不編碼可不可以呢?這是因為在電腦中存儲數據的基本單位是1個位元組,即 ...
JAVA WEB 亂碼問題解析
亂碼原因
在Java Web開發過程中,經常遇到亂碼的問題,造成亂碼的原因,概括起來就是對字元編碼和解碼的方式不匹配。
既然亂碼的原因是字元編碼與解碼的方式不匹配,那麼為什麼我們一定要對字元進行編碼,不編碼可不可以呢?這是因為在電腦中存儲數據的基本單位是1個位元組,即8個bit,那麼它所能表達的字元的最多有28=256個,而在我們現實社會中存在的字元(漢字、英文、其他文字等等)遠遠多餘這個數字,所以為瞭解決字元與位元組的矛盾,對字元進行編碼處理才能存儲在電腦中。
編碼與解碼
在電腦中常見的編碼方式有ASCII、ISO-8859-1、GB2312、UTF-16、UTF-8幾種編碼方式。
ASCII碼是使用一個位元組的低7位來表示的,所以共能表達的字元最多有27=128個。ISO-8859-1是ISO組織基於ASCII碼的基礎上擴展來的,相容ASCII碼,涵蓋了大多數西歐字元。ISO8859-1使用一個位元組來表示,所以其能表達的字元最多有256個。GB2312,採用了雙位元組編碼,編碼範圍是A1-F7,其中A1-A9是符號區,B0-F7是漢字區,包含6763個漢字。GBK是為了擴展GB2312編碼,並加入了更多的漢字,總能表達的漢字有21003個。UTF-16是採用定長的編碼方式,無論什麼字元都採用2個位元組進行表示,這也是JAVA記憶體中字元的存儲格式。與UTF-16相反,UTF-8採用了變長的編碼方式,不同的類型的字元可以由1-6個位元組組成。
下麵以字元串“日向雛田”來看一下在電腦中不同編碼方式的編碼,如下圖。
亂碼分析與解決
對於JAVA WEB中亂碼問題,我們劃分位請求導致的亂碼和響應導致的亂碼,對於不同的亂碼我們要分析其亂碼原因,即字元編碼的方式是什麼,解碼的方式是什麼。
對於由於請求導致的亂碼我們要分析Http請求,查看其編碼方式,由於HTTP請求分為Get請求和Post請求,我們接下來分別對其進行討論。
對於Get請求,是瀏覽器預設的請求方式,和表單提交時設置為“Get”時的提交方式。我們通過火狐瀏覽器我們查看其具體內容如下:
地址欄為:
請求內容為:
通過上面請求我們可以看到,GET請求中查詢字元串放在了請求行中存放,發送到WEB伺服器中,通過“日向雛田”編碼我們可以看到,瀏覽器對該字元串採用的編碼方式為“UTF-8”。
查看伺服器代碼我們可以看到亂碼(如下圖),這是因為伺服器在接受到該字元串編碼後的數據預設通過ISO-8859-1的方式進行解碼,所以造成了編碼與解碼的方式不統一。
解決方案如下:
首先獲取字元串user解碼前的編碼,然後指定該字元串的編碼方式,如下圖:
解決方案示意圖如下:
在Java web開發過程中,我們在超鏈接中傳遞參數,經常遇到中文的情況。對此情況下,我們需要對中文進行編碼,我們可以設置為UTF-8,解碼方案同上。
<a href="${pageContext.request.contextPath}/Test?user=<%=URLEncoder.encode("日向雛田", "UTF-8")%>">點擊</a>
對於Post請求,是表單提交時設置為“Post”時的提交方式。我們通過火狐瀏覽器我們查看其具體內容如下:
地址欄及其頁面為:
post請求內容為:
由上圖我們可以知道,在post請求中,將請求內容直接放在請求體中發送給web伺服器,編碼方式為“utf-8”。
在此響應Servlet中,doPost方法體如下:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String user=request.getParameter("user"); System.out.println(user);//輸出為æ¥åéç° }
此處亂碼的原因依然時在代碼getParameter(“user”)時,web伺服器採用預設的解碼方案“ISO-8859-1”進行解碼,導致了編碼與解碼方案的不同意,解決方案可以採用get請求亂碼的解決方案,但是還有一種更為簡單的解決方案,直接指定方法體的編碼/解碼方案為“utf-8”。方案如下。
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); //設置請求體的編碼/解碼方案為UTF-8 但是請求行的編碼解碼方案不會受影響 String user=request.getParameter("user"); System.out.println(user); //輸出為日向雛田 }
以上對於請求導致的亂碼情況分析完畢。
在影響導致的亂碼中,web伺服器會將響應的內容寫入響應體中,返回給客戶端並不會涉及到狀態行中的情況。如向瀏覽器輸出”HelloWorld“其響應如下圖所說。
對於響應導致的亂碼我們不得不涉及到四個方法,如下:
response.setHeader("Content-Type", "text/html;cahrset=utf-8");//設置發送到客戶端的響應的內容類型和響應內容的編碼類型(響應體的編碼類型)
response.setCharacterEncoding("utf-8");//設置響應體的編碼類型
response.getWriter(); //獲取響應的輸出字元流 response.getOutputStream(); //獲取響應的輸出位元組流
對於設置響應體的編碼類型,如response.setHeader("Content-Type", "text/html;cahrset=utf-8");與response.setCharacterEncoding("utf-8");這2個方法設置的編碼方式等效,若沒有設置響應體的編碼方式,則預設為ISO-8859-1,而且後面設置響應體字元的編碼方式會迭代前面的設置編碼的方式。這兩個方法均在getWriter方法前有效,在getWriter方法設置編碼的方法會無效。
但是這2個方法卻有點不同,即setHeader("Content-Type", "text/html;cahrset=utf-8")這個方法瀏覽器會自動採用該響應體的編碼方式進行解碼,而setCharacterEncoding()該方法並不是所有的瀏覽器都會採用該方法的編碼方式進行解碼,下麵對這2個方法進行測試,效果如下:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Content-Type", "text/html;charset=utf-8"); response.getWriter().write("日向雛田"); }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); response.getWriter().write("日向雛田"); }
從上面可以看到第一個方法對於瀏覽器來說,支持的較好,提倡採用第一種方法設置響應體的字元編碼方式。
對於獲取響應字元輸出流的方法,如果在此之前沒有設置響應體的編碼方式,那麼預設為null,即ISO-8859-1方式進行編碼。而且後面設置的編碼方式會覆蓋前面設置的編碼方式。在getWriter()方法之後設置的編碼無效。
對於獲取響應輸出位元組流,我們在輸出字元串時,我們需要設置字元串的編碼方式如果沒有那麼預設ISO-8859-1。
對於前面2個輸出流,由於只有一個輸出緩存,所以這兩個方法互斥。
以上,為了保證響應無亂碼,需要保證字元編碼和解碼方法的統一,方案如下:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 方案1 // response.setHeader("Content-Type", "text/html;charset=utf-8"); // response.getWriter().write("日向雛田"); // 方案2 // response.getOutputStream().write("日向雛田".getBytes("UTF-8")); // 方案1,2互斥 }
此外在Java web開發過程中,我們還會遇到當進行文件下載時,中文文件名導致的問題,如下圖所示:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String realPath=this.getServletContext().getRealPath("/src/日向雛田.jpg"); String fileName=realPath.substring(realPath.lastIndexOf('\\')+1); response.setHeader("content-disposition", "attachment;filename="+fileName); InputStream is=new FileInputStream(new File(realPath)); OutputStream os=response.getOutputStream(); byte[] buff=new byte[1024]; int len=0; while((len=is.read(buff))>0){ os.write(buff, 0, len); } os.close(); is.close(); }
採用火狐瀏覽器進行測試,查看頁面效果,及其響應結果如下:
經過查看響應頭分析,下載文件名存放在響應頭中,且對於中文文字沒有採用UTF-8、UTF-16、GBK等等能識別中文的編碼,那麼對於中文文件名導致採用哪種編碼方式呢?查看REF 7578得知,在此處採用ASCII編碼,但是REF規定,如果不可避免的要使用非ASCII碼的字元,程式員應該均勻的使用UTF-8,來最小化交互操作的問題。
所以,解決方案就是把文件名編碼成UTF-8,傳遞給響應頭,瀏覽器(部分)預設對該文件名進行UTF-8解碼處理。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String realPath=this.getServletContext().getRealPath("/src/日向雛田.jpg"); String fileName=realPath.substring(realPath.lastIndexOf('\\')+1); String utf_8Name=URLEncoder.encode(fileName,"utf-8");//解決方案 response.setHeader("content-disposition", "attachment;filename="+utf_8Name); InputStream is=new FileInputStream(new File(realPath)); OutputStream os=response.getOutputStream(); byte[] buff=new byte[1024]; int len=0; while((len=is.read(buff))>0){ os.write(buff, 0, len); } os.close(); is.close(); }
效果如下:其中火狐瀏覽器並沒有對其解碼
文章最後欣賞老婆的美照: