-------------------------------------------------------------------------------------------------------------------------------------------------------
原地址: http://www.csdn.net/develop/article/17/17204.shtm
http://www.csdn.net/develop/article/17/17222.shtm
http://www.csdn.net/develop/article/17/17223.shtm
http://www.csdn.net/develop/article/17/17225.shtm
http://www.csdn.net/develop/article/17/17233.shtm
Tomcat的中文處理(一)
看到很多朋友問關於中文的處理問題,下面我們以tomcat4.0爲servlet,jsp引擎來說說unicode的處理。
1) 從客戶端接受請求
當客戶端請求tomcat的一個jsp文檔的時候,tomcat會構造相應的httpServletRequest實現類的實例來代表客戶端,通過對流servletInputStream讀,我們可以得到客戶端來的數據。
在jsp中我們通常使用的request.getParameter()來得到參數的值,這個函數的背後到底怎麼樣的呢?怎麼樣對String編碼的呢?
通過tomcat的httpServletRequest實現類源代碼考察:
public String getParameter(String name)
{
parseParameters();/////////處理parameters
String values[] = (String[])parameters.get(name);//得到該參數名字對應的Object(是一個數組)
if(values != null)
{
return values[0];
} else
{
return null;
}
}
其中parameters是request的一個map類型的數據成員,用來存放接受到的客戶端的數據。也就是說每當客戶端請求的時候,tomcat構造一個request實例,該實例有一個parameters用來存放從servlet實例的寫入流的讀來的客戶端的數據。
從上面的代碼知道最重要的的是parseParameters()函數,它是來處理parameters的。
下面來看看:
protected void parseParameters()
{
if(parsed)
{
return;///如果處理過了,就不要處理了
}
ParameterMap results = parameters;/////構造parameters對象的本地引用
if(results == null)
{
results = new ParameterMap();//////如果沒有實例
}
results.setLocked(false);
String encoding = getCharacterEncoding();//////////////////////////得到httpServeltRequest的編碼
if(encoding == null)
{
encoding = "ISO-8859-1";//////////如果沒有指定httpServeltRequest的編碼採用"ISO-8859-1"
}
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
RequestUtil.parseParameters(results, queryString, encoding);//////////////////////處理編碼
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
is.read(buf, len, max - len); //////////////////////從流中讀取數據
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
RequestUtil.parseParameters(results, buf, encoding);///////////////////////////////////處理編碼
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
parameters = results;//////重置引用
}
下面再來看看RequestUtil.parseParameters(results, buf, encoding);/的處理:
在此就不貼源代碼了,
RequestUtil.parseParameters(results, buf, encoding)的處理中對於buf byte數組進行處理,構造key和value,就是參數名字和參數值:
while(ix < data.length)
{
byte c = data[ix++];
switch((char)c)
{
case 38: // '&'
value = new String(data, 0, ox, encoding);
if(key != null)
{
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case 61: // '='
key = new String(data, 0, ox, encoding);
ox = 0;
break;
case 43: // '+'
data[ox++] = 32;
break;
case 37: // '%'
data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
break;
}
}
if(key != null)
{
value = new String(data, 0, ox, encoding);
putMapEntry(map, key, value);
}
顯然對於參數名字和參數的值都是採用的new String(data, 0, ox, encoding);方法來使用指定的編碼方式構造的。
結論:我們不難看出如果沒有指定request的編碼方式,那麼從客戶端接受到的參數的名字和參數值都是以iso-8859-1編碼的String的。
也就是說我們在jsp的頁面中的表單元素中給出的參數值在通過request.getParamter()得到後的String是以iso-8859-1編碼的。
而且我們看看tomcat爲jsp產生的java文件知道,對於在jsp定義的沒有指定編碼方式的String的時候,tomcat是使用的iso-8859-1方式的,而不是系統默認的。
比如:
<%
String name=new String(“你好”);或者String name=”你好”;/////都是使用的iso-8859-1的編碼方式的。
System.out.println(name);/////////////////就會產生亂碼的。(因爲Console使用的系統的默認編碼的,中文系統是gb2321,日文是MS932).
%>
下篇我們介紹httpServletResponse的處理
Tomcat的中文處理(二):
上篇我們介紹了tomcat是怎麼對接收到字符進行編碼的,現在我們來看當向客戶端寫html文檔的時候到底發生了什麼?
tomcate在向客戶端寫出數據的時候,使用的是response的輸出流來實現的。但是jsp是怎樣使用response的流的呢?
在使用JSP內含對象out輸出的時候,out是一個JspWriter實現類的對象實例,JspWriterImpl(ServletResponse response, int sz, boolean autoFlush)是一個該類的構造函數,其使用到了response,在JspWriterImpl內部還有一個java.io.Writer對象實例的引用,在使用JspWriter(JSP的out對象)寫出數據的時候,會調用如下的函數來初始化
protected void initOut() throws IOException
{
if(out == null)
{
out = response.getWriter();/////////初始化 java.io.Writer對象
}
}來初始化該內部對象的。
然後在jspWriter的各個輸出數據的函數的實現中就是調用上面的java.io.Writer對象的方法的。
所以不論是jsp或者是servlet,對客戶端寫出html的時候,都是通過response.getWriter();來得到的字符流或者由getOutputStream()得到2進制流的。
一個response存在一個字符流,也存在一個2進制流,但是在同一時刻只能打開使用一個流的。至於兩者的關係,我們在後面介紹。Jsp的out對象就是response的字符流的。
同樣的request也存在一個字符流和一個2進制流,但是在同一時刻只能打開使用一個流的。
response的兩個流的關係
我們來考察response的實現類的getOutputStream()和getWriter函數的實現:
public ServletOutputStream getOutputStream() throws IOException
{
。。。。。。。。。。。。。。。。。。。。。
stream = createOutputStream();///創建response的2進制的輸出流
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
return stream;
}
public PrintWriter getWriter() throws IOException
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
ResponseStream newStream = (ResponseStream)createOutputStream();////////創建2進制流
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr, newStream);///得到response的字符輸出流
。。。。。。。。。。。。。。。。。。。。。。。。。。
}
}
顯然,我們的字符流就是從2進制流轉化而來的
還有兩個函數要注意:
public String getCharacterEncoding()//////response的編碼,默認是ISO-8859-1的
{
if(encoding == null)//////////////////////////////////如果沒有指定編碼
{
return "ISO-8859-1";
} else
{
return encoding;
}
}
public void setContentType(String type);設置response的類型和編碼
{
。。。。。。。。。。。。。
encoding = RequestUtil.parseCharacterEncoding(type);////////得到指定的編碼
if(encoding == null)
{
encoding = "ISO-8859-1";//////////////////////////如果沒有指定編碼方式
}
} else
if(encoding != null)
{
contentType = type + ";charset=" + encoding;
}
}
好了,現在我們知道了在寫出字符的時候使用的response的字符流(不管是jsp或者servlet),也就是使用的OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
注意的是newStream是response的2進制流的實現。
所以我們還得看看OutputStreamWriter的實現:
考察OutputStreamWriter的源代碼,他有一個StreamEncoder 類型的對象,就是依靠他來轉換編碼的;
StreamEncoder是由sun公司提供的,它有一個
public static StreamEncoder forOutputStreamWriter(OutputStream outputstream, Object obj, String s)來得到StreamEncoder對象實例。
對於jsp,servlet來說在構造他的時候 outputstream參數是response的2進制流,obj是OutputStreamWriter對象,s就是編碼方式的名字。其實得到是一個StreamEncoder的子類的對象實例,
return new CharsetSE(outputstream, obj, Charset.forName(s1)); CharsetSE是StreamEncoder的子類。
他有一個如下的函數來實現編碼轉換的:
void implWrite(char ac[], int i, int j)throws IOException /////// ac是要輸出String的char數組
{
CharBuffer charbuffer = CharBuffer.wrap(ac, i, j);
。。。。。。。。。。。。。。。。。。。。。。。
CoderResult coderresult = encoder.encode(charbuffer, bb, false);/////bb是ByteBuffer,存放編碼後的byte緩衝區
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
writeBytes();///////////////////////////////將bb轉化到byte數組寫入到response的2進制流中
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
至此,我們瞭解了tomcat背後的編碼轉換過程
Tomcat的中文處理(三):
前面廢話講過了,現在我們來分析幾個例子:
1)jsp中如果使用了:
<%@ page contentType="text/html; charset=Shift_JIS" %>
他其實就是指定了response的類型和字符的編碼方式,上面指定了response的字符編碼是是Shift_JIS。
在jsp中構造String的時候,如果沒有明確指定String的編碼,String使用的編碼就是charset指定的;如果charset沒有指定字符的編碼的話,那麼話,就使用ISO-8859-1
注意的是如果沒有指定requset的編碼,那麼從request得到的String都是iso-8859-1編碼的。(上一篇已經講過了。),他和charset是沒有關係的。
如果要輸出的String的編碼和response的編碼不一樣的話,就很可能出現亂碼的情況。
舉個例子:
<%@ page contentType="text/html; charset=GB2312" %>///////////////指定response的編碼爲中文簡體,那麼所有的要輸出的字符都要使用和GB2312相適應的編碼
<html>
<head><title></title>
</head>
<body>
<%
String name=request.getParameter("name");////////得到客戶端的參數值,沒有指定request的編碼,所以它是編碼爲iso-8859-1的String的。
String name1=new String(name.getBytes("ISO-8859-1"),"GB2312");//////////轉化爲中文簡體的編碼
String name2="你好";/////直接定義String,使用reponse的編碼,這裏是GB2312的。
String name21=new String(name2.getBytes("ISO-8859-1")," GB2312");////////從name2轉化
System.out.println("name1 is GB2312"+name1);
System.out.println("name is ISO-8859-1"+name);
System.out.println("name21 is 直接"+name21);
System.out.println("我們大家");
%>
<form action="./B.jsp" method="POST">
<input type="text" name="name" value="<%=name1%>">
<input type="submit">
</form>
<hr>
name1 is GB2312 <%=name1%><br>
name is ISO-8859-1 <%=name%><br>
name21 is 直接<%=name21%><br>
<%="我們大家"%></body>
</html>
結果:
console中:(他對應response的編碼是GB2312的,日文系統是MS932)
name1 is GB2312 你好//////////name1是name轉化來的,是GB2312的,所以正常顯示
name is ISO-8859-1????/////////////name是ISO-8859-1的不能正常顯示的
name21 is 直接???????????????????////////////////由於name2是GB2312編碼的,在name21 =new String(name2.getBytes("GB2312"),"MS932"))發生了錯誤的轉化,所以不能正常的現實,如果將ISO-8859-1換為GB2312就可以了。
我們大家//////////////////////////////////jsp中定義的string是採用<%@ page contentType="text/html; charset= GB2312" %>指定的編碼,如果沒有指定,就使用iso-8859-1的。
可以看到我們在ie中看到的結果是一樣的。
下面我們將<%@ page contentType="text/html; charset=Shift_JIS" %>去掉。
結果:
console (這個時候,Console的編碼是GB2312,所以編碼爲GB2312的字符能顯示,由於在jsp中構造的String此時使用的iso-8859-1,所以不能顯示)
name1 is GB2312你好
name is ISO-8859-1????
name21 is ???? 你好/////////////////name2的編碼此時爲iso-8859-1,所以轉化來的name21是正確的
????????
ie (這個時候,response的編碼是iso-8859-1,所以編碼爲iso-8859-1的能顯示,由於在jsp中構造的String此時使用的iso-8859-1,所以也能顯示)
name1 is GB2312??
name is ISO-8859-1 你好
name21 is 直接 ???????????????????
我們大家
顯然不一樣了結果!!!!
Tomcat的中文處理(四):
2)在servlet和其他java文件中的字符
在這種情況下,構造的String使用的系統默認的編碼方式的。
但是在servlet中從request得到的字符,如果沒有指定request的編碼,那麼就是得到的一個編碼方式爲iso-8859-1的字符,在servlet中,如果沒有指定response的編碼方式(通過setContentType),那麼,response使用的iso-8859-1的編碼方式。
例子:
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorldExample extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
String name=request.getParameter("name");/////得到name參數的value
response.setContentType("text/html ");///不設置編碼,此時response使用iso-8859-1的編碼
PrintWriter out = response.getWriter();//得到字符流,此時的編碼為iso-8859-1
out.println("<html>");
out.println("<head>");
String title="你好";/////構造一個String,注意的是雖然此時沒有爲response指定編碼,但是在servlet中構造的String使用的系統默認的編碼的。
out.println("<title>sdsfdsfsdfds</title>");
out.println("</head>");
out.println("<body bgcolor=/"white/">");
out.println("我們大家<br>");///////輸出一個編碼爲本地默認的String到response中,但是此時response的編碼是iso-8859-1的,所以出現亂碼得
out.println("title is "+title);////title是系統默認的編碼,得到亂碼
out.println("<br>name is "+name);/////name是request的來的,正確顯示
out.println("</body>");
out.println("</html>");
System.out.println("你好");////由於Console是系統默認編碼,所以正確顯示
System.out.println("title is "+title);/////title是默認編碼的,正常顯示
System.out.println("name is "+name);///////name是iso-8859-1的編碼的,亂碼
}
}
如果我們在response.setContentType("text/html ");改為:response.setContentType("text/html ;charset=GB2312");那麼,IE的輸出和Console的輸出是一樣的。
總結:
1. 在jsp中<%@ page contentType="text/html; charset=A" %>如果指定了,那麼在改jsp中所有構造的String(不是引用),如果沒有指定編碼,那麼這些String的編碼是A的。
從request的得到的String如果沒有指定request的編碼的話,他是iso-8859-1的
從別的地方得到的String是使用原來初始的編碼的,比如從數據庫得到String,如果數據庫的編碼是B,那麼該String的編碼是B而不是A的,也不是系統默認的。
此時,如果要輸出的String的編碼不是A,那麼,很可能顯示亂碼的,所以首先要將String正確轉化爲編碼A的String,然後輸出。
2. 在jsp中<%@ page contentType="text/html; charset=A" %>沒有指定,那麼相當於指定了<%@ page contentType="text/html; charset=ISO-8859-1" %>
3. Servelte中如果執行了像 response.setContentType("text/html;charset=A");説明將response的字符輸出流編碼設置爲A,所有要輸出的String的編碼要轉化爲A的,否則會得到亂碼的。
Servelet中從request得到的String的編碼和jsp中一樣的,但是在servlet java文件中構造的String是使用的系統默認的編碼的。在servelt中從外部得到的String 是使用原來的編碼的,比如從編碼爲B的數據庫得到的數據是編碼爲B的,不是A,也不是系統默認的編碼。
後語: 雖然我們使用的tomcat來作說明,其他的jsp,servlet引擎其實現的方法也差不多!
使用filter來改變request的編碼
在前面的文章裏面,我們討論了在tomcat下的jsp和servlet的字符編碼問題!
知道當沒有指定request的編碼的時候,從客戶端得到的數據是iso-8859-1編碼的(request.getParameter()得到傳遞的參數值);
但是我們怎麼來改變request的編碼呢?
方法有很多種!
比如:在getRequestDispatcher("/jsp/jsptoserv/hello.jsp").forward(request, response);之前修改
request的編碼,那麼在jsp/jsptoserv/hello.jsp中得到的參數值就是制定的編碼的字符。
本文我們使用Filter來修改request的編碼!
1)首先編寫filter類:
package myFilter;
import java.io.IOException;
import javax.servlet.*;
public class ChangeCharsetFilter implements Filter {
protected String encoding = null;/////要制定的編碼,在web.xml中配置
protected FilterConfig filterConfig = null;
public void destroy() {
this.encoding = null;
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request.getCharacterEncoding() == null){
String encoding = getEncoding();////得到指定的編碼名字
if (encoding != null)
request.setCharacterEncoding(encoding);////設置request的編碼
}
chain.doFilter(request, response);///有機會執行下一個filter
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");///得到在web.xml中配置的編碼
}
protected String getEncoding() {
return (this.encoding);///得到指定的編碼
}
}
2。編輯web.xml文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>SetCharacterEncoding</filter-name>
<filter-class>myFilter.ChangeCharsetFilter </filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GB2312</param-value>//////指定編碼爲GB2312
</init-param>
</filter>
<filter-mapping>
<filter-name>SetCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>////////對於所有的request改變其編碼
</filter-mapping>
</web-app>
///
3。
寫一個a.jsp
<%@ page contentType="text/html; charset=GB2312" %>
<html>
<head></head>
<body>
<%
String name=request.getParameter("name");///本來這裏得到字符是iso-8859-1編碼的,不能直接
在Console中輸出的,但是現在改變了request的編碼方式,此時的name的編碼是GB2312,所以能正確在Console中顯示的。
System.out.println(name);
%>
<form action="a.jsp" method="post">
<input type="text" name="name">
<input type="submit">
</form>
<%=name%>
</body>
</html>
完!
關於中文處理的問題就寫這些了!