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

尚学堂.张志宇.乱码分析_04_读取硬盘上的文件.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来代表
 所有iso-8859-1字符都被收录到unicode
 有些unicode字符(同时也被iso-8859-1收录的)没有对应的gbk编码,即使能找到对应的全角的字符。也已经是另外一个unicode字符了。

2 准备知识
2.1 “错误”这两个字符的gbk编码是B4ED CEF3
可以靠3个方法来得到gbk编码。
第一:java程序中可以靠下面这段代码得到
String s="错误";
byte [] bytes = s.getBytes("GBK");
for(byte b:bytes){
    System.out.format("%x",b);
}

第二:可以到GBK编码表去验证
http://www.microsoft.com/globaldev/reference/dbcs/936.mspx
http://www.microsoft.com/globaldev/reference/dbcs/936/936_B4.mspx



第三:也可以用记事本创建一个文件,输入这两个字符“错误”,然后存储成默认的ansi编码 (ansi在咱们的winxp操作系统就是指的gbk) 。再用ultraEdit软件打开,切换到16进制形式,也可以得到gbk编码

2.2 “错误”这两个字符的utf-16编码是9519 8bef
可以靠下面这段代码得到
这里得解释一下:输出的结果是feff95198bef
其中,feff指的是大头方式。后面的95198bef才是utf-16编码的内容
String s="错误";
byte [] bytes = s.getBytes("utf-16");
for(byte b:bytes){
    System.out.format("%x",b);
}
或者靠下面这段代码得到utf-16编码,这里直接把内存里面的内容输出。JVM内部,就是用utf-16编码来表示这个字符串的。

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;
}

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

3 现象描述
假设有个类叫TestFileInputStream_1,读取一个同目录下面的文本文件01.txt。这个文本文件只有两个汉字:“错误”
运行java TestFileInputStream_1
得到的结果是?í?ó
为什么?
//注意:类名不要和jdk里面的类名重复
import java.io.*;

class TestFileInputStream_1 {
public static void main(String[] args) throws Throwable {
FileInputStream in = new FileInputStream("01.txt");
int b = 0;
b = in.read();
System.out.print((char) b);
b = in.read();
System.out.print((char) b);
b = in.read();
System.out.print((char) b);
b = in.read();
System.out.print((char) b);
}
}

4 分析
4.1 文件01.txt是按照gbk编码存储的
FileInputStream in = new FileInputStream("01.txt");
首先,01.txt文件的内容是按照gbk编码存储的。所以硬盘上的内容应该是0xB4ED 0xCEF3
4.2 读入一个字节
其次看这两句话
int b = 0;
b = in.read();
这会读入“错”这个字符的gbk编码的前一个字节。B4
读进来的内容赋值给了int类型的b,内存里面是
b00000000 00000000 00000000 10110100
4.3 转换成char
(char) b
接下来强制转换成char,char在内存里面是两个字节,把这4个字节的头2个截去。只留下后面2个字节。
内存00000000 10110100 (0x00B4)

那么这两个字节0x00B4代表哪个字符呢?
JVM使用UTF-16编码代表一个unicode字符。普通字符的UTF-16编码和代码点是对应的。代码点是00B4的那个字符是 。可以到《U0080.pdf》里去看下。这个pdf文档是从unicode官方网站上下载下来的。
在unicode中代码点是00B4的这个字符在对应着iso-8859-1里面的字符。
unicode和iso-8859-1两种字符集的对应关系可以看这个文件来验证:
《8859-1.TXT》这是从官网上下载来的。
可以看到unicode的0x00B4字符对应iso-8859-1的0xB4字符
4.4 打印char
接下来
System.out.print((char) b);
System.out是PrintStream类型的,查看api文档,关于print方法是这样描述的:
中文:
打印字符。按照平台的默认字符编码将字符转换为一个或多个字节,并完全以 write(int) 方法的方式写入这些字节。
英文:
Print a character. The character is translated into one or more bytes according to the platform's default character encoding, and these bytes are written in exactly the manner of the write(int) method.

为了验证传到dos窗口的是一些gbk编码。可以运行这段代码
FileOutputStream fo = new FileOutputStream("02.txt");
PrintStream ps = new PrintStream(fo);
System.setOut(ps);
int b ;
b= 0xB4;
System.out.print((char)b);
b = 0xED;
System.out.print((char)b);
ps.close();
用ultraEdit软件打开02.ext,切换到16进制表示,看到的是3FA8AA,这些就是gbk编码!

也就是说,print方法不会默认把内存里面的字节内容传给dos窗口,而是会进行某种编码,(我们用的winxp操作系统默认编码就是gbk),把编码的结果(就是一些字节呗)传给dos窗口。
编码结果是3f,这个过程可以靠下面的代码来模拟。
char[] chars = new char[]{(char)0xb4};
byte[] bytes = new String(chars).getBytes("gbk");
for(byte b:bytes){
    System.out.format("%x",b);
}

可是按照gbk编码得到的结果是3f为什么呢?
这个问题也困扰了我很久,下面说原因。
首先,按照gbk编码的意思是到gbk编码表里面找找看有没有这个字符。只要gbk编码表没有收录这个字符。JVM不会抛异常,而是统统默认返回一个字节3f。
那么如何知道gbk里面有没有收录这个字符?gbk编码和unicode字符集中的代码点是有对应关系的。查看这个文件《CP936.TXT》,这是从官方网站上下载下来的。这个文件收录了所有的gbk编码和unicode字符的代码点的对照关系。
打开这个文件,在第二列搜索一下00B4,是不是没有这个unicode字符?也就是说这个字符没有被gbk编码表收录!
gbk编码表兼容ascii,编码得到的3f在ascii里面是“?”这个字符。所以您看到的是一个“?”
到这里验证:
http://msdn.microsoft.com/zh-cn/goglobal/cc305153(en-us).aspx


ISO 8859-1,正式编号为ISO/IEC 8859-1:1998,又称Latin-1或“西欧语言”,是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,藉以供使用附加符号的拉丁字母语言使用。
但这个unicode字符0x00B4居然在GBK没有被收录?
你可能感觉很意外。我也是。
再跟我做个实验。

打开这个地址:
http://zh.wikipedia.org/w/index.php?title=ISO/IEC_8859-1&variant=zh-cn
找到B4位置上的这个字符。拷贝这个字符。
´
新建一个文本文件。粘贴,存盘。

如果无法打开这个网址也可以这样。新建一个文本文件,打开,按住alt键,输入180,然后抬起alt键盘。呵呵,雕虫小技而已。(180是0xB4的10进制)


这是你会得到一个提示: 
不管这个提示。点击确定。然后关闭再打开。你会看到这个字符还在,只是样子有点怪,比刚才的大了些。



用ultrait软件打开,切换到16进制表示方式。看到了哪两个字节?
是A1E4。这是某个字符的gbk编码。再打开下面的网址去看看是哪个字符?
http://www.microsoft.com/globaldev/reference/dbcs/936/936_A1.mspx

好奇怪把。字符是有。但不是原来那个。因为原来拷贝粘贴的是ISO 8859-1里的0xB4这个字符,这个字符对应着unicode里面的0x00B4字符,gbk没有对应着unicode里面的0x00B4字符的字符。但是记事本帮你转化成了unicode里面的另外一个字符。0x2032字符!

打开《U0080.pdf》可以看到0x00B4字符。这个是半角的
打开《U2000.pdf》可以看到0x2032字符。这个是全角的。
记事本帮你做自动的转换,因为没法在gbk中找到对应的0x00B4字符,自动帮你转换为全角的0x2032字符。
换句话说,gbk只收录了全角的没有收录半角的。
但是java不会帮你做这个转换。如果gbk编码表没有收录这个字符。会默认返回一个字节3f。
3f在ascii里面是“?”这个字符。所以您看到的是一个“?”

4.5 来总结一下
 文件存储为gbk编码0xB4ED 0xCEF3
 读到第一个字节到内存里,0xB4
 再转换成char,内存里面是0x00B4
 再编码成gbk,3f
 dos窗口在解码成字符显示出来。?
unicode iso-8859-1 gbk
半角0x00B4 0xB4 未收录(3f)
全角0x2032 - A1E4


4.6 第二个读入的字节有所不同。
再执行这句话
b = in.read();
这会读入“错”这个字符的gbk编码的第二个字节。0xED
读进来的内容赋值给了int类型的b,内存里面是
b00000000 00000000 00000000 11101101
4.7 转换成char
(char) b
接下来强制转换成char,char在内存里面是两个字节,把这4个字节的头2个截去。只留下后面2个字节。
内存00000000 11101101 (0x00ED)

那么这两个字节00000000 11101101代表哪个字符呢?
JVM使用UTF-16编码代表一个unicode字符。普通字符的UTF-16编码和代码点是对应的。代码点是0x00ED的那个字符是 。可以到《U0080.pdf》里去看下。这个pdf文档是从unicode官方网站上下载下来的。
在unicode中代码点是0x00ED的这个字符在对应着iso-8859-1里面的字符。
unicode和iso-8859-1两种字符集的对应关系可以看这个文件来验证:
《8859-1.TXT》这是从官网上下载来的。
可以看到iso-8859-1的0xED字符对应unicode的0x00ED字符

4.8 打印char
接下来
System.out.print((char) b);
根据刚才的分析,print方法不会默认把内存里面的字节内容传给dos窗口,而是会按照gbk进行编码,把编码的结果(就是一些字节呗)传给dos窗口。
这次,得到的编码结果不再是3F,而是A8AA,前面可以找到验证代码
这个过程可以靠下面的代码来模拟。
char[] chars = new char[]{(char)0xED};
byte[] bytes = new String(chars).getBytes("gbk");
for(byte b:bytes){
    System.out.format("%x",b);
}

这次为什么是A8AA而不是3f呢?
因为gbk编码表收录了这个字符嘛。当然会找到相应的gbk编码!
如何证明?很简单,查看这个文件《CP936.TXT》,在第二列搜索一下00ED,是不是找到了!
又很意外?
再跟我做个实验。

打开这个地址:
http://zh.wikipedia.org/w/index.php?title=ISO/IEC_8859-1&variant=zh-cn
找到ED位置上的这个字符。拷贝这个字符。
´
新建一个文本文件。粘贴,存盘。
如果无法打开这个网址也可以这样。新建一个文本文件,打开,按住alt键,输入237,然后抬起alt键盘。 (237是0xED的10进制)

这次很顺利,再关闭,再打开好像还是刚才那个字符。
用ultraedit软件打开,切换到16进制表示方式。看到了A8AA两个字节。
这就是该unicode字符的gbk编码。再打开下面的网址去看看是哪个字符?
再打开gbk编码表来验证。http://www.microsoft.com/globaldev/reference/dbcs/936/936_A8.mspx
是不是看到了这个字符?


JVM会把编码的结果0xA8AA传给dos窗口,dos窗口再按照gbk解码,正确的显示出来。
由于gbk收录了这个字符,所以您能够看到正确的结果。
4.9 来总结一下
 文件存储为gbk编码0xB4ED 0xCEF3
 读第二个字节到内存里,0xED
 再装换成char,内存里面是0x00ED
 再编码成gbk,A8AA
 dos窗口在解码成字符显示出来。í
unicode iso-8859-1 gbk
半角0x00ED 0xED A8AA
全角 - -
好像unicode没有定义该字符对应的全角的字符。我不确定这个。如果哪个同学在unicode字符集中找到了对应的全角的字符。希望你告诉我一下。

后两个字节自己分析吧。


5 参考
http://zh.wikipedia.org/w/index.php?title=ISO/IEC_8859-1&variant=zh-cn
http://msdn.microsoft.com/zh-cn/goglobal/cc305153(en-us).aspx

分享到:
评论

相关推荐

    尚学堂_Sping_0200_IOC_Introduction

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

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

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

    尚学堂_Spring_0700_IOC_Collections

    一种常见的方式是通过XML配置文件来定义集合的元素,例如: ```xml &lt;bean id="exampleBean" class="com.example.ExampleClass"&gt; &lt;value&gt;Value1 &lt;value&gt;Value2 ``` 在这里,`listProperty`是`...

    尚学堂_Spring_0300_IOC_Injection_Type

    当我们在类的属性上使用@Autowired时,Spring容器会自动寻找与该属性类型匹配的bean,并将其注入到该属性中。如果存在多个匹配的bean,可以通过@Qualifier注解指定特定的bean。此外,还可以通过@Primary注解标记一个...

    尚学堂_Spring_0800_IOC_Auto_Wire

    自动装配减少了XML配置文件的复杂性,Spring会根据bean的属性类型或名称尝试匹配并注入相应的依赖。 5. **@Autowired注解**:这是Spring提供的一个注解,用于标记字段、方法或构造函数,告诉Spring容器自动寻找合适...

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

    Hadoop的核心组件包括HDFS(分布式文件系统)和MapReduce(一种分布式计算模型)。由于Hadoop是用Java编写的,因此掌握Java编程语言是学习Hadoop的基础。 对于零基础的学习者,以下是你需要掌握的基础知识: 1. **...

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

    9. **第9章_IO流技术**:涵盖了输入/输出流的基本概念,如字节流和字符流,以及文件操作、对象序列化和缓冲区的使用,这是进行文件读写和数据交换的重要技能。 10. **第10章_多线程**:介绍了多线程编程,包括线程...

    尚学堂_Spring_0600_IOC_Bean_Scope

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

    Java设计模式

    在提供的压缩包文件中,尚学堂的系列视频教程可能包括了对责任链模式的深入讲解。例如: 1. 尚学堂_设计模式_责任链_01.avi:这个视频可能从基础开始介绍责任链模式,阐述其核心思想,比如如何定义处理器对象,以及...

    尚学堂_Spring_0100_模拟Spring

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

    尚学堂Java笔记.pdf

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

    java学习顺序.doc

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

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

    1. **HDFS(Hadoop Distributed File System)**:是一个分布式文件系统,用于存储大量的数据。 2. **MapReduce**:是一种编程模型,用于处理和生成大规模数据集。 3. **YARN(Yet Another Resource Negotiator)**...

    Lucene_ShangXueTang.rar_文章/文档_Java_

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

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

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

    hibernate资料3

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

    1.【尚学堂】全新2022版WEB前端HTML5.zip

    HTML5是超文本标记语言(HTML)的最新版本,它在原有的HTML4基础上引入了众多新的特性和改进,以适应现代互联网的发展需求。这个教程可能是为初学者设计的,帮助他们从零开始掌握前端开发的基础,直至达到精通的水平...

    尚学堂_Hibernate3.3.2_项目源码

    《尚学堂Hibernate3.3.2项目源码详解》 Hibernate,作为一个强大的对象关系映射(ORM)框架,是Java开发中的重要工具,它简化了数据库操作,将复杂的SQL语句与Java对象进行关联,使得开发者可以专注于业务逻辑而...

    hibernate资料5

    在实际应用中,初始化Hibernate需要配置几个关键文件,包括hibernate.cfg.xml(配置文件)、实体类(与数据库表对应的Java类)以及持久化映射文件(.hbm.xml)。在hibernate.cfg.xml中,我们需要指定数据库连接信息...

Global site tag (gtag.js) - Google Analytics