论坛首页 编程语言技术论坛

尚学堂.张志宇.乱码分析_08_pageEncoding.doc

浏览 2485 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-01-31   最后修改:2012-03-26
JVM

 

 

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)

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics