`
bjrobin
  • 浏览: 13687 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

    博客分类:
  • java
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) 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();

 

       InputStream ins = s.getInputStream();

       int i = 0;

       while ((i = ins.read ()) != -1) {

           System. out .format( "%x " , i);

       }

       ins.close();

       bw.close();

       ins.close();

       s.close();

    }

}

 

 

 

48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b d a 53 65 72 76 65 72 3a 20 41 70 61 63 68 65 2d 43 6f 79 6f 74 65 2f 31 2e 31 d a 53 65 74 2d 43 6f 6f 6b 69 65 3a 20 4a 53 45 53 53 49 4f 4e 49 44 3d 33 43 36 32 36 42 33 35 43 43 43 38 42 32 31 37 38 37 36 34 38 30 37 36 42 38 42 42 38 46 41 44 3b 20 50 61 74 68 3d 2f 6d 79 d a 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 68 74 6d 6c 3b 63 68 61 72 73 65 74 3d 75 74 66 2d 38 d a 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 3a 20 31 35 d a 44 61 74 65 3a 20 54 75 65 2c 20 32 35 20 41 75 67 20 32 30 30 39 20 30 33 3a 32 37 3a 34 30 20 47 4d 54 d a d a d a ef bf bd ef bf bd ef bf bd ef bf bd 21

 

ef bf bd ef bf bd ef bf bd ef bf bd 21 ,这些内容就是浏览器收到的内容。

 

l  由于浏览器收到的头信息是: Content-Type: text/html;charset=utf-8 ,浏览器就会默认 按照 utf-8 进行解码。看看浏览器的字符编码是不是默认选在了 utf-8

解码之后得到的是不存在的字符,所以

eclipse 中显示为;得到的是 ????!

在浏览器看到的是 ����!

4.   场景 3

l  如果客户端浏览器把这些字节 ef bf bd ef bf bd ef bf bd ef bf bd 21 按照 gbk 来解码,得到的是

锟斤拷锟斤拷 !

 

5.   验证代码

import java.io.UnsupportedEncodingException;

 

public class TestC {

    public static void main(String[] args) throws UnsupportedEncodingException {

       //jsp 文件默认存储为 ansi ,即 jbk 编码: ce d2 c3 c7 21

       byte [] bytes = new byte []{( byte )0xCE,( byte )0xD2,( byte )0xc3 ,( byte )0xc7 ,( byte )0x21 };

 

       // 如果按照 gbk 来解码,能够得到正确的中文

       System. out .println( "-------- 如果按照 gbk 来解码,能够得到正确的中文 " );

       String s = new String(bytes, "gbk" );

       System. out .println(s);

      

       // 但是由于在 jsp 文件中指明 pageEncoding utf-8

       // 所以 tomcat 会把硬盘上 1.jsp jbk 编码错误的当作 utf-8 来解码。

       String s1 = new String(bytes, "utf-8" );

       System. out .println( "--------tomcat 会把硬盘上 1.jsp jbk 编码错误的当作 utf-8 来解码 " );

       System. out .println(s1);

      

       System. out .println( "--------servlet 源文件 h 和字节码文件都是这些 utf-8 编码 " );

       // 将解码之后得到的错误的字符串再编码成 utf-8 存在转换的 servlet 源文件中。

       //servlet class 文件也是这些 utf-8 编码

       byte [] bytes1 = s1.getBytes( "utf-8" );

       for ( byte b:bytes1){

           System. out .format( "%x " ,b);

       }

       System. out .println();

      

 

       //servlet 载入执行,将字节码中的 utf-8 编码解码成字符串对象。

       // 如果再按照 utf-16 编码,我们可以看到内存里面的 utf-16 编码。

       System. out .println( "-------- 内存里面的 utf-16 编码 " );

       String s2 = new String(bytes1, "utf-8" );

       byte [] bytes_uft_16 = s2.getBytes( "utf-16" );

       for ( byte b:bytes_uft_16){

           System. out .format( "%x " ,b);

       }

       System. out .println();

      

      

       // 被编码成 utf-8 ,传到浏览器客户端

       System. out .println( "-------- 被编码成 utf-8 ,传到浏览器客户端 " );

       byte [] bytes2 = s1.getBytes( "utf-8" );

       for ( byte b:bytes2){

           System. out .format( "%x " ,b);

       }

 

       // 客户端按照 utf-16 解码

       String s3 = new String(bytes1, "utf-8" );

       System. out .println();

       System. out .println( "-------- 客户端按照 utf-16 解码 " );

       System. out .println(s3);

    }

}

 

 

 

 

 

分享到:
评论

相关推荐

    hadoop大数据培训零基础学习hadoop-北京尚学堂.docx

    hadoop大数据培训零基础学习hadoop-北京尚学堂.docxhadoop大数据培训零基础学习hadoop-北京尚学堂.docx

    尚学堂_Sping_0200_IOC_Introduction

    【标题】:尚学堂_Spring_0200_IOC_Introduction 【内容详解】: Spring 框架是 Java 应用开发中的一个重要组件,它以其依赖注入(Dependency Injection,简称 DI)和控制反转(Inversion of Control,简称 IOC)...

    尚学堂_Spring_0700_IOC_Collections

    总之,"Spring_0700_IOC_Collections"涵盖了Spring框架中关于IoC容器处理集合依赖的重要知识,包括XML配置、Java配置、注解注入以及源码分析等多个方面。通过学习这部分内容,开发者可以更深入地理解Spring的IoC机制...

    尚学堂_Spring_0300_IOC_Injection_Type

    标题中的"Spring_0300_IOC_Injection_Type"指的是Spring框架中的依赖注入(Dependency Injection,简称DI)机制,特别是关于类型注入(Type Injection)的知识点。在Spring框架中,依赖注入是核心特性之一,它使得...

    百战程序员答案javase基础部分

    JavaSE(Java Standard Edition)是Java编程语言的基础部分,涵盖了从基本语法到核心特性的一系列知识。本资料集“百战程序员答案javase基础部分”是针对Java初学者和进阶者的宝贵资源,包含了从第一章到第十章的...

    尚学堂_Spring_0800_IOC_Auto_Wire

    标题中的"Spring_0800_IOC_Auto_Wire"指的是Spring框架中的依赖注入(Dependency Injection,简称DI)特性,特别是自动装配(Auto Wiring)。在Spring框架中,IOC(Inversion of Control,控制反转)是一种设计模式...

    尚学堂_Spring_0600_IOC_Bean_Scope

    标题中的“Spring_0600_IOC_Bean_Scope”涉及到的是Spring框架中的核心概念——控制反转(Inversion of Control, 简称IOC)以及Bean的作用域(Scope)。在这个主题下,我们将深入探讨Spring如何通过IOC管理Bean的...

    hadoop大数据培训零基础学习hadoop-北京尚学堂.pdf

    最后,建议配合尚学堂的肖斌Hadoop经典视频教程以及云计算极限班的课程进行系统学习,这些资源将提供详细的指导和实例,帮助你逐步深入理解和掌握Hadoop大数据技术。同时,持续实践和参与社区讨论也是提升技能的重要...

    Java设计模式

    1. 尚学堂_设计模式_责任链_01.avi:这个视频可能从基础开始介绍责任链模式,阐述其核心思想,比如如何定义处理器对象,以及如何建立和配置责任链。 2. 尚学堂_设计模式_责任链_02.avi:接着,可能会讲解如何在实际...

    尚学堂_Spring_0100_模拟Spring

    《尚学堂_Spring_0100_模拟Spring》 在深入探讨Spring框架之前,我们需要先理解Spring的核心理念。Spring是一个开源的Java平台,它主要为构建企业级应用提供了全面的编程和配置模型。它的核心特性是依赖注入...

    Lucene_ShangXueTang.rar_文章/文档_Java_

    《尚学堂全文检索系统——深入理解Lucene》 在IT领域,搜索引擎技术是不可或缺的一部分,尤其是在大数据时代,如何高效地从海量信息中快速找到所需内容成为了一项挑战。Java平台上的Lucene,作为一款强大的开源全文...

    hibernate资料3

    接着,"008_尚学堂马士兵_Java视频教程_Hibernate3.3.2_常见OR框架简介.avi"中,马士兵老师会对比分析其他的ORM框架,如iBatis等,帮助你了解它们与Hibernate的区别和联系,进一步巩固对Hibernate的理解。...

    java学习顺序.doc

    尚学堂科技提供了相关的视频教程,如"张志宇_SERVLET_JSP_视频教程",还有JDBC、MySQL和Tomcat的使用,这些是构建Web应用的基础。通过"JAVA_网上商城项目视频讲解"这样的实战教程,可以提高实际操作能力。 深入J2EE...

    尚学堂Java笔记.pdf

    尚学堂Java笔记.pdf 本资源主要讲述Java语言的基础知识和应用,包括J2SDK、JRE、JDK、classpath、path、Java应用程序、递归调用等内容。 一、J2SDK和JRE Java2 Software Development Kit(J2SDK)是Java开发需要...

    441769547180350是男人就撑20秒【尚学堂】.zip

    441769547180350是男人就撑20秒【尚学堂】.zip

    全网最新尚学堂hadoop大数据视频教程.doc

    1. **数据分析**:通过对大量数据的分析,帮助企业做出更明智的决策。 2. **搜索引擎**:Hadoop可以用来索引和搜索互联网上的大量网页数据。 3. **推荐系统**:通过分析用户的浏览历史和购买记录,向用户推荐可能感...

    JAVA视频学习顺序

    - **尚学堂科技_张志宇_SERVLET_JSP_视频教程_第一版**:介绍了Servlet和JSP的基础知识,这是构建动态网页的基础。 - **尚学堂科技_马士兵_JAVA_系列视频教程_BBS_2007**:通过实际案例,让学员了解如何运用所学知识...

    Hibernate3.3.2_Java例子精简JAR包

    本例子通過學習“002_尚学堂马士兵_Java视频教程_Hibernate3.3.2_HelloWorld.avi”教程實踐的小例子。 整理出来的精简JAR包,共八个。分别: antlr-2.7.6.jar、commons-collections-3.1.jar、dom4j-1.6.1.jar、...

    hibernate资料5

    在“hibernate资料5”中,我们有两个关键的学习资源:016_尚学堂马士兵_Java视频教程_Hibernate3.3.2_hibernate基础配置.avi 和 017_尚学堂马士兵_Java视频教程_Hibernate3.3.2_Annotation字段映射位置.avi。...

Global site tag (gtag.js) - Google Analytics