发表时间:2012-01-31
最后修改:2012-03-26
1.
结论
l
pageEncoding
会影响读取
jsp
时的解码过程。
l
pageEncoding
会影响转换后的
servlet
的
response.setContentType
这句话。
l
对于不合法的
utf-8
编码进行解码,每个字节都会得到
ff fd
。这个代码点代表在
unicode
中不存在或者不可表示的字符
|
场景
1
|
场景
2
|
场景
3
|
|
1.jsp
存储格式
|
gbk
|
gbk
|
存为
gbk
|
|
<%@page pageEncoding
|
gbk
|
utf-8
|
pageEncoding="utf-8"
|
|
浏览器字符编码
|
gbk
|
utf-8(
默认
)
|
gbk
(手工设定)
|
|
转换后的
servlet
|
我们
|
����!
|
����!
|
|
浏览器
|
我们
|
����!
|
锟斤拷锟斤拷
!
|
|
2.
场景
1
没有乱码,分析略过
3.
场景
2
1.jsp
<%@page pageEncoding=" utf-8"%>
<%=("
我们
!")%>
|
详细分析
l
首先,
1.jsp
文件默认存储为
ansi
,即
jbk
,
”
我们
!”
编码成
jbk
得到如下字节
ce d2 c3 c7 21
l
在
tomcat
将
jsp
转换为
servlet
的过程中,需要先读入
jsp
中的字符信息,因为
jsp
是按照
gbk
来存储的,如果
tomcat
按照
gbk
来解码,能够得到正确的中文
;但是由于在
jsp
文件中指明
pageEncoding
为
utf-8
,所以
tomcat
会把硬盘上的
jbk
编码错误的当作
utf-8
来解码。
将解码之后得到的错误的字符串再编码成
utf-8
存储在转换后的
servlet
源文件
_1_jsp.java
中
.
n
用
ultraedit
打开
tomcat
工作目录
work\Catalina\localhost\my\org\apache\jsp
目录下的
_1_jsp.java
,切换到
16
进制可以看到这些字节,
ef bf bd ef bf bd ef bf bd ef bf bd 21
,
这些字节就是错误的
utf-8
编码。
n
为什么会编码成这些字节?
先来回忆一下
uft-8
的编码规则。
UTF-8
用
1
~
4
个字节来表示代码点。表示方式如下:
UCS-2
(UCS-4)
|
位序列
|
第一字节
|
第二字节
|
第三字节
|
第四字节
|
U+0000
.. U+007F
|
00000000-0xxxxxxx
|
0xxxxxxx
|
|
|
|
U+0080
.. U+07FF
|
00000xxx-xxyyyyyy
|
110xxxxx
|
10yyyyyy
|
|
|
U+0800
.. U+FFFF
|
xxxxyyyy-yyzzzzzz
|
1110xxxx
|
10yyyyyy
|
10zzzzzz
|
|
U+10000..U+1FFFFF
|
00000000-000wwwxx-
xxxxyyyy-yyzzzzzzz
|
11110www
|
10xxxxxx
|
10yyyyyy
|
10zzzzzz
|
将
ce d2 c3
c7
解码的意思是说,这些字节是合法的
utf-8
编码,现在要还原成正确的字符。
问题是:
ce d2 c3 c7
是合法的
utf-8
编码吗?
ce d2 c3 c7
对应的
2
进制是:
11001110
11010010
11000011
11000111
先看
ce
对应的二进制是
11001110
,
110
开头说明是符合
uft-8
的编码规则第二行,范围是
U+0080 .. U+07FF
的
unicode
会被编码成
110xxxxx 10yyyyyy ,
紧跟着下来的第二个字节应该是
10
开头,但是,接下来的
d2
却是
110
开头的。说明这不是个合法的
utf-8
编码。
ce d2 c3 c7
不是合法的
utf-8
编码!
或者可以这样说,这些所谓的
utf8
编码对应的字符在
unicode
中就根本不存在。!
在接着往下看!
虽然错误解码了。但是字符串对象还是得到了。只不过不是我们想要的而已。
在错误解码后得到的字符串应该在内存中表示为
utf-16
编码吧。
既然这些不合法的
utf-8
编码
对应的字符在
unicode
中就根本不存在。那内存里怎么办?
先来看
ef bf bd
的二进制是
11101111 10111111 10111101
根据
utf-8
编码规则,它代表的是
unicode
中的
ff fd
代码点。
打开
UFFF0.pdf
来验证下。(这个文档可以去
unicode
官网去下载)
看看
unicode
关于
fffd
这个代码点的解释:
used to replace an incoming character whose value is
unknown
or un representable in
Unicode.
哦,用来代表在
unicode
中未知的或者无法表示的字符
来稍微总结一下:
我们要对
ce d2 c3 c7
按照
utf-8
进行解码,当然这些字节不是合法的
utf-8
编码。
解码的目的是得到一些个字符。我们得到了所谓的字符。一共
4
个,这
4
个字符在
unicode
里面的代码点都是
ff fd
。在
unicode
中
ff fd
这个代码点代表未知的或者无法表示的字符。
对于这个代码点,当然有合法的
utf-8
编码,即
ef bf bd
|
l
打开
_1_jsp.class
这个文件,同样可以发现
ef bf bd ef bf bd ef bf bd ef bf bd 21
这些字节,说明
class
文件也错误的
utf-8
编码!
l
servlet
开始运行,会将字节码里面的错误的
utf-8
编码按照
utf-8
进行解码。得到的当然是错误的内容。错误解码得到的错误的字符串在内存里标识为
utf-16
编码。这时候内存里面应该是
ff fd ff fd ff fd ff fd 0 21
l
由于
jsp
中咱们是这么写的
<%@page pageEncoding="
utf-8"%>
,这样会造成转换
servlet
时自动产生这句
response.setContentType("text/html;charset=utf-8");
。这句代码的意思是输出到浏览器之前,按照
utf-8
进行编码。这将得到
ef bf bd ef bf bd ef bf bd ef bf bd
21
,这些直接被传到浏览器。
n
可以靠下面的代码来验证,客户端浏览器是不是得到这些字节呢?
import
java.io.BufferedReader;
import
java.io.BufferedWriter
;
import
java.io.InputStreamReader;
import
java.io.OutputStreamWriter;
import
java.net.Socket;
public
class
TestHTTP2 {
public
static
void
main(String[] args)
throws
Exception
{
Socket s =
new
Socket(
"127.0.0.1"
, 80);
BufferedWriter
bw =
new
BufferedWriter
(
new
OutputStreamWriter(s
.getOutputStream()));
bw.write(
"GET /my/1.jsp HTTP/1.1"
);
bw.newLine();
bw.write(
"Host: 127.0.0.1"
);
bw.newLine();
bw.write(
"Content-Type: text/html"
);
bw.newLine();
bw.newLine();
bw.flush();
BufferedReader br =
new
BufferedReader(
new
InputStreamReader(s
.getInputStream()));
String str =
null
;
while
((str = br.readLine()) !=
null
) {
System.
out
.println(str);
}
bw.close();
br.close();
s.close();
}
}
|
这段程序得到下面的结果。“锟斤拷锟斤拷
!
”
这个怪怪的字符串是按照
gbk
解码得到的结果。
这个结果没法直观的了解客户接收的字节信息。
HTTP/1.1 200 OK
Server:
Apache-Coyote/1.1
Set-Cookie:
JSESSIONID=7D6AE9D95A8D2562AD6B19A4DE0EE08C; Path=/my
Content-Type: text/html;charset=utf-8
Content-Length:
15
Date: Tue, 25 Aug
2009 03:29:09 GMT
锟斤拷锟斤拷
!
|
程序稍微改动一下,原来用的是转换流。现在只用基于字节的输入流,不做任何转换。
import
java.io.BufferedWriter;
import
java.io.InputStream;
import
java.io.OutputStreamWriter;
import
java.net.Socket;
public
class
TestHTTP1 {
public
static
void
main(String[] args)
|