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

尚学堂.张志宇.乱码分析_03_读取servlet参数.doc

    博客分类:
  • java
阅读更多
1 重要结论
J2SE 5.0 用的是Unicode 4.0
J2SE 6.0 用的也是Unicode 4.0
Java编程语言用16位的编码代表文本。使用UTF-16编码.
一个 char 表示一个 UTF-16 代码单元
并不是一个char代表一个字符,因为一个增补字符需要2个char来代表

2 web.xml
    <servlet>
      <servlet-name>TestInitServlet</servlet-name>
      <servlet-class>TestInitServlet</servlet-class>
     
      <init-param>
      <param-name>name</param-name>
      <param-value>我们</param-value>
      </init-param>
      <init-param>
      <param-name>age</param-name>
      <param-value>30</param-value>
      </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>TestInitServlet</servlet-name>
        <url-pattern>/TestInitServlet</url-pattern>
    </servlet-mapping>

3 原理

import java.io.UnsupportedEncodingException;

public class Test1 {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "我们";
System.out.println(s);

// ced2 c3c7
System.out.println("--------编码成GBK得到如下字节----------");
byte[] bytes = s.getBytes("GBK");
for (int i = 0; i < bytes.length; i++) {
System.out.println(Integer.toHexString(bytes[i]));
}
// 内存里面是utf16编码,6211 4eec。参看U4E00.pdf
System.out.println("--------utf-16----------");
System.out.println(getUnicodeFromStr(s));
System.out.println("--------得到正确的中文----------");
// 再组装成正确的字符串
String ss = new String(s.getBytes("GBK"), "GBK");
System.out.println(ss);
}

public static String getUnicodeFromStr(String s) {
String retS = "";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
retS += String.format("%1$04x", (int) c) + " ";
}
return retS;
}
}

代码分析:
首先明确的是
“我们”的gbk编码是:ced2 c3c7
可以到GBK编码表去验证
http://www.microsoft.com/globaldev/reference/dbcs/936.mspx

“我们”的utf-16编码是:6211 4eec
可以参考从unicode官方网站上下载下来的《U4E00.pdf》确认此事。
其实U4E00.pdf里面查到的只是代码点。
而utf-16编码,对于普通字符来说(即不是增补字符),和代码点是一致的。

当执行这句话
String s = "我们";
内存里面,局部变量s指向一个字符串对象,这个对象是由utf-16编码序列组成的。即内存里面是这么表示的:s62 11 4e ec
在unicode字符集里面,6211这个代码点代表“我”这个字符,4eec这个代码点代表“们”这个字符。而普通字符的utf-16编码和代码点又是一致的。

接下来当我们执行这句话
System.out.println(s);
又发生了这些事情。
第一, jvm把这个字符串传给了dos窗口,或者说传给了eclipse里面的console。
那么,jvm传给dos窗口的是那些字节呢?是ced2 c3c7
也就是说,jvm不会把内存的表示方式传给dos窗口,也就是说不会把utf-16编码传递给dos窗口,而是把这个字符串的gbk编码传递给了dos窗口。
还有,为什么是把gbk的编码而不是其他的编码传给dos窗口呢?因为gbk是咱们的winxp操作系统的默认编码。
第二, dos窗口,把接受到的字节(这里已经是gbk编码了)按照gbk编码组装成字符串并且显示出来。

接下来执行这句话
byte[] bytes = s.getBytes("GBK");
这句话的意思是得到这个字符串的gbk编码。
jvm能确认局部变量s代表的是“我们”这两个字符吗?当然能。因为现在内存里表示为s62 11 4e ec,这在unicode里面代表的就是“我们”这两个字符。
要得到“我们”这两个字符的gbk编码,只需要到gbk编码表里面去查找就可以了。
所以这一步会得到gbk的编码ced2 c3c7

接着:
System.out.println(getUnicodeFromStr(s));
这句话是用来确认内存里面是不是s62 11 4e ec,
getUnicodeFromStr这个方法里面的代码是取得组成这个字符串的所有char,然后返回表示这些个char值的固定格式的字符串。
不要忘记,字符串是由char组成的。每一个char代表一个utf-16编码单元,但一个char未必表示一个字符,因为java用两个char来表示一个增补字符。

接下来。
String ss = new String(s.getBytes("GBK"), "GBK");
这句话的意思是把这些字节组装成字符串。
这些字节是ced2 c3c7
组装工作很简单。到gbk编码表里面去匹配就可以了。
是不是组装成功取决于这些字节是不是正确的有效的gbk编码。因为gbk编码表里面很多代码点是没有定义字符的。
ced2 c3c7是有效的gbk编码,当然可以正确组装成“我们”这个字符串。
这个字符串在jvm里依然得用utf-16编码来表示。所以,内存里面是这么表示的:ss62 11 4e ec

最后一句话的情形前面分析过了。
System.out.println(ss);
JVM先得到这个字符串的gbk编码然后传给dos窗口,让后dos窗口按照gbk组装成字符串然后显示出来。

4 情况1
下面这个servlet,没有调用resp.setContentType方法,也没有调用new String(s.getBytes("iso8859-1"), "gbk");这句话。
结果网页上显示的是正确的中文。为什么呢?
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestInitServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
String s = this.getInitParameter("name");
out.println(s);
out.close();
}
}

下面来分析:
web.xml确实以gbk编码存储到硬盘上的。用ultraedit软件打开它,切换到16进制表示方式,可以看到ced2 c3c7这几个字节
但tomcat默认把它当作iso8859-1的文本来读入内存的。
iso8859-1的特点是一个字节表示一个字符。
而且iso8859-1的编码和unicode的代码点是兼容的。每个iso8859-1的编码(一个字节)的前面再加一个字节0x00,就成了unicode代码点。
可以到unicode官方网站验证iso8859-1的编码和unicode的代码点之间的对应关系。
http://www.unicode.org/Public/MAPPINGS/
或者查看已经下载下来的文件:《ISOIEC 8859-1与Unicode的关系.mht》
而普通字符的代码点就是utf-16编码。
所以,当tomcat默认把它当作iso8859-1的文本来读入内存时候,会把每个字节当作一个iso8859-1字符,然后在内存里表示为这个字符的utf-16编码。所以这个时候,内存里面局部变量s指向了一个字符串对象,这个对象是utf-16编码,即s00ce 00d2 00c3 00c7

内存里表示成什么内容很关键。这时内存里面的字符串对象 和执行这句话String s = "我们";得到的字符串对象已经完全不同了。

接下来的这句话
out.println(s);
把这个字符串传到客户端浏览器。JVM这里可不是把内存里面的内容传到浏览器。JVM必须以某种编码形式来传递这个字符串。因为没有指明到底以什么编码来传递。所以默认是iso8859-1。
所以tomcat帮忙调用s.getBytes("iso8859-1"),得到4个字节,ce d2 c3 c7,让后把这4个字节传给客户端。
客户端浏览器以为接受到的是gbk的编码,所以默认按照gbk编码组装成字符串

这个servlet可以用下面的程序来模拟:
import java.io.UnsupportedEncodingException;

public class Test2 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 读入web.xml,内存里面的unicode编码是这些字节内容:
byte[] bytes = new byte[4];
bytes[0] = (byte) 0xce;
bytes[1] = (byte) 0xd2;
bytes[2] = (byte) 0xc3;
bytes[3] = (byte) 0xc7;
String s = new String(bytes, "iso8859-1");
System.out.println(getUnicodeFromStr(s));
//默认先编码成iso8859-1,再传到客户端
byte[] bytes1 = s.getBytes("iso8859-1");
for (int i = 0; i < bytes1.length; i++) {
System.out.print(String.format("%1$02x", bytes1[i]) + " ");
}
System.out.println();
//客户端浏览器按照GBK编码组装成字符
System.out.println(new String(bytes, "GBK"));
}
public static String getUnicodeFromStr(String s) {
String retS = "";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
retS += String.format("%1$04x", (int) c) + " ";
}
return retS;
}
}


5 情况2
下面这个servlet,调用了resp.setContentType方法,但没有调用new String(s.getBytes("iso8859-1"), "gbk");这句话。
结果网页上显示的是乱码。为什么呢?

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestInitServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=gbk");
PrintWriter out = resp.getWriter();
String s = this.getInitParameter("name");
out.println(s);
out.close();
}
}
这种情形和上种情形只有一个地方不同,就是加了resp.setContentType("text/html;charset=gbk");
这句话。这句话的意思是说传到客户端之前,先按照gbk编码。

内存里面现在是这样的。s00ce 00d2 00c3 00c7,把这个字符串对象再编码成gbk是错误的。因为,在unicode中,00ce所代表的是iso8859-1那个字符,在gbk编码表里面并没有收录进来,所以没法编码成gbk,而且如果找不到匹配的字符,jvm不会报错,而是返回错误的结果。3f 3f 3f 3f。咱们希望它是ce d2 c3 c7。

如果内存里面是s62 11 4e ec, 再编码成gbk,才会得到我们想要的正确的结果。ced2 c3c7

好了,tomcat把错误的结果,3f 3f 3f 3f传给客户端浏览器。浏览器再把3f 3f 3f 3f这四个字节按照gbk组装成字符串显示出来,当然是乱码了。因为gbk编码表里面没有任何一个字符的编码是3f3f。

你就是尝试设置浏览器的字符编码设置也没用。比如设置为iso8859-1。首先iso8859-1没有定义3f,因为要和ascii兼容。而ascii字符集里规定,3f是“?”这个字符。
这个servlet可以用下面的程序来模拟:
import java.io.UnsupportedEncodingException;

public class Test3 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 读入web.xml,内存里面的unicode编码是这些字节内容:
byte[] bytes = new byte[4];
bytes[0] = (byte) 0xce;
bytes[1] = (byte) 0xd2;
bytes[2] = (byte) 0xc3;
bytes[3] = (byte) 0xc7;

String s = new String(bytes, "iso8859-1");
// 打出来当然是乱码
System.out.println(s);
System.out.println(getUnicodeFromStr(s));

System.out.println("--------错误的做法----------");

// 先编码成GBK,再传到客户端
byte[] bytes_error = s.getBytes("GBK");
for (int i = 0; i < bytes_error.length; i++) {
//System.out.println(Integer.toHexString(bytes_error[i]));
System.out.print(String.format("%1$02x", bytes_error[i]) + " ");
}
// 浏览器根据这些字节组装成gbk:
String s_error = new String(bytes_error, "GBK");
System.out.println(s_error);

}
public static String getUnicodeFromStr(String s) {
String retS = "";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
retS += String.format("%1$04x", (int) c) + " ";
}
return retS;
}
}


6 情况3
下面这个servlet,调用了resp.setContentType方法,也调用了new String(s.getBytes("iso8859-1"), "gbk");这句话。
结果网页上显示的是正确的中文。为什么呢?

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestInitServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=gbk");
PrintWriter out = resp.getWriter();
String s = this.getInitParameter("name");
String ss = new String(s.getBytes("iso8859-1"), "gbk");
out.println(ss);
out.close();
}
}
这种情况,多了两条关键的语句
resp.setContentType("text/html;charset=gbk");
String ss = new String(s.getBytes("iso8859-1"), "gbk");

刚开始,内存里面是s00ce 00d2 00c3 00c7
s.getBytes("iso8859-1")会得到ce d2 c3 c7
这4个字节正是我们想要的gbk编码。咱们的目的是把这四个字节ce d2 c3 c7传到客户端。
如果你直接调用out.println(s);那就达不到目的。前面一种情况已经分析过了。

String ss = new String(s.getBytes("iso8859-1"), "gbk");
这句话是把ce d2 c3 c7这四个字按照gbk编码节组装成字符串。在gbk编码表里面,ced2代表我们的我字,c3c7代表我们的们字。字符串能够正确组装。但正确组装的这个字符串,内存里面要表示为这两个字符的utf-16编码。即内存里面的局部变量s指向的字符串对象为:ss62 11 4e ec

看看这两个字符串的不同:
s00ce 00d2 00c3 00c7
ss62 11 4e ec
s代表的是4个西欧字符
ss代表的是2个汉字字符。

当执行这句话的时候,
out.println(ss);
由于resp.setContentType("text/html;charset=gbk");这句话的作用,
tomcat会把ss代表的两个汉字字符编码成gbk编码。ce d2 c3 c7
如果没有resp.setContentType方法调用,则会被编码成iso8859-1.

最后,ce d2 c3 c7这四个字节传到浏览器,浏览器把这按照gbk编码组装。所以我们看到了正确的结果
0
1
分享到:
评论

相关推荐

    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设计模式

    3. 尚学堂_设计模式_责任链_03.avi:随着课程的深入,可能讨论到如何处理多个处理器的情况,以及如何在链中跳过某些处理器,或者在处理器之间进行条件判断。 4. 尚学堂_设计模式_责任链_04.avi:可能进一步探讨责任...

    尚学堂_Spring_0100_模拟Spring

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

    java学习顺序.doc

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

    Lucene_ShangXueTang.rar_文章/文档_Java_

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

    尚学堂Java笔记.pdf

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

    hibernate资料3

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

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

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

    JAVA视频学习顺序

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

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

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

    我的ServletJSP课件

    在Servlet中,我们通常通过`web.xml`文件来配置Servlet,比如定义Servlet类、映射URL路径、设置初始化参数等。这文档可能详细解释了如何编写和理解`web.xml`中的配置元素。 "会话跟踪技术 - 副本.doc"和"会话跟踪...

    SERVLET_JSP.01.ppt

    尚学堂servlet_jsp视频教程中使用的ppt课件,SERVLET_JSP.01.ppt

Global site tag (gtag.js) - Google Analytics