`
Dustin
  • 浏览: 314719 次
  • 性别: Icon_minigender_1
  • 来自: 广州/成都
社区版块
存档分类
最新评论

深入浅出Java中文问题(二)字符,字节和编码

阅读更多
   通过引言我们可以知道,java中文问题是由于在输入输出时字符集之间的错位而产生的。那么,当前比较通用的字符集有哪些呢?它们都有些什么特点?它们之间有什么区别和联系?为什么字符集错位会导致出现中文问题呢?要回答这些问题,我们就要对字符集有一个系统的了解。网络上有一篇文章对此作了系统详细的描述,上面的文字相当简明到位。摘抄部分至此,作为系列文章的第二篇,也作以后复习之用。这里并没有把整个文章摘抄下来,主要是为了系列文章的连续性。


字符,字节和编码

原文地址:http://www.regexlab.com/zh/encoding.htm

级别:中级

摘要:本文介绍了字符与编码的发展过程,相关概念的正确理解。举例说明了一些实际应用中,编码的实现方法。然后,本文讲述了通常对字符与编码的几种误解,由于这些误解而导致乱码产生的原因,以及消除乱码的办法。本文的内容涵盖了“中文问题”,“乱码问题”。

掌握编码问题的关键是正确地理解相关概念,编码所涉及的技术其实是很简单的。因此,阅读本文时需要慢读多想,多思考。

引言

“字符与编码”是一个被经常讨论的话题。即使这样,时常出现的乱码仍然困扰着大家。虽然我们有很多的办法可以用来消除乱码,但我们并不一定理解这些办法的内在原理。而有的乱码产生的原因,实际上由于底层代码本身有问题所导致的。因此,不仅是初学者会对字符编码感到模糊,有的底层开发人员同样对字符编码缺乏准确的理解。

1. 编码问题的由来,相关概念的理解

1.1 字符与编码的发展

从计算机对多国语言的支持角度看,大致可以分为三个阶段:

 
系统内码
说明
系统
阶段一
ASCII
计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。
英文 DOS
阶段二
ANSI编码
(本地化)
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。

不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。

不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
中文 DOS,中文 Windows 95/98,日文 Windows 95/98
阶段三
UNICODE
(国际化)
为了使国际间信息交流更加方便,国际组织制定了UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。
Windows NT/2000/XPLinuxJava

字符串在内存中的存放方法:

在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,"Bob123" 在内存中为:

42 6F 62 31 32 33 00
B o b 1 2 3 \0

在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:

D6 D0 CE C4 31 32 33 00
1 2 3 \0

在 UNICODE 被采用之后,计算机存放字符串时,改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:

2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字节在前
1 2 3 \0  

一共占 10 个字节。

1.2 字符,字节,字符串

理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:

  概念描述 举例
字符 人们使用的记号,抽象意义上的一个符号。 '1', '中', 'a', '$', '¥', ……
字节 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 0x01, 0x45, 0xFA, ……
ANSI
字符串
在内存中,如果“字符”是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串或者多字节字符串 "中文123"
(占7字节)
UNICODE
字符串
在内存中,如果“字符”是以在 UNICODE 中的序号存在的,那么我们称这种字符串为 UNICODE 字符串或者宽字节字符串 L"中文123"
(占10字节)

由于不同 ANSI 编码所规定的标准是不相同的,因此,对于一个给定的多字节字符串,我们必须知道它采用的是哪一种编码规则,才能够知道它包含了哪些“字符”。而对于 UNICODE 字符串来说,不管在什么环境下,它所代表的“字符”内容总是不变的。

1.3 字符集与编码

各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准(GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义:

  1. 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。
  2. 规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。

各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。

UNICODE 字符集”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

1.4 常用的编码简介

简单介绍一下常用的编码规则,为后边的章节做一个准备。在这里,我们根据编码规则的特点,把所有的编码分成三类:

分类 编码标准 说明
单字节字符编码 ISO-8859-1 最简单的编码规则,每一个字节直接作为一个 UNICODE 字符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。

反之,将 UNICODE 字符串通过 iso-8859-1 转化为字节串时,只能正常转化 0~255 范围的字符。
ANSI 编码 GB2312,
BIG5,
Shift_JIS,
ISO-8859-2 ……
把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。

反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 转化为字符串时,将得到 [0x4E2D] 一个字符,即 '中' 字。

“ANSI 编码”的特点:
1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。
UNICODE 编码 UTF-8,
UTF-16, UnicodeBig ……
与“ANSI 编码”类似的,把字符串通过 UNICODE 编码转化成“字节串”时,一个 UNICODE 字符可能转化成一个字节或多个字节。

与“ANSI 编码”不同的是:
1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。

     我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”,由于它们是可以通过计算得到的,因此,在特殊的场合,我们可以去了解某一种“UNICODE 编码”是怎样的规则。

2. 几种误解,以及乱码产生的原因和解决办法

2.1 容易产生的误解

  对编码的误解
误解一 在将“字节串”转化成“UNICODE 字符串”时,比如在读取文本文件时,或者通过网络传输文本时,容易将“字节串”简单地作为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。

而实际上,在非英文的环境中,应该将“字节串”作为 ANSI 字符串,采用适当的编码来得到 UNICODE 字符串,有可能“多个字节”才能得到“一个字符”。

通常,一直在英文环境下做开发的程序员们,容易有这种误解。
误解二 在 DOS,Windows 98 等非 UNICODE 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维:“字符串的编码”。

当 UNICODE 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。

不少的人都有这个误解。

第一种误解,往往是导致乱码产生的原因。第二种误解,往往导致本来容易纠正的乱码问题变得更复杂。

在这里,我们可以看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作,得到原始的“字节串”。然后再使用正确的 ANSI 编码,比如 string = new String(bytes, "GB2312"),来得到正确的“UNICODE 字符串”。

 

2.2 非 UNICODE 程序在不同语言环境间移植时的乱码

非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同,将会导致 ANSI 字符串的显示失败。

比如,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串,那么当在中文环境下运行时,界面上将可以显示正常的日文。

由于客观原因,有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件,这时我们可以采用一些工具,比如,南极星,AppLocale 等,暂时的模拟不同的语言环境。

3. 几种错误理解的纠正

误解:“ISO-8859-1 是国际编码?”

非也。iso-8859-1 只是单字节字符集中最简单的一种,也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”,而又不知道它是哪一种 ANSI 编码时,先暂时地把“每一个字节”作为“一个字符”进行转化,不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节串。

误解:“Java 中,怎样知道某个字符串的内码?”

Java 中,字符串类 java.lang.String 处理的是 UNICODE 字符串,不是 ANSI 字符串。我们只需要把字符串作为“抽象的符号的串”来看待。因此不存在字符串的内码的问题

分享到:
评论
2 楼 a_nuo 2007-05-10  
厉害
这么详细
1 楼 boogie 2007-05-09  
很好的文章!

相关推荐

    深入浅出Java语言程序设计

    本资源“深入浅出Java语言程序设计”旨在帮助读者全面理解和掌握Java编程技术,无论你是初学者还是有一定经验的程序员,都能从中受益。 1. **基础概念** - **Java简介**:Java是由Sun Microsystems开发的面向对象...

    【IT十八掌徐培成】Java基础第09天-005.String-字符集编码.zip

    总的来说,徐培成老师的课程深入浅出地讲解了Java中的字符串和字符集编码,帮助开发者避免常见的编码问题,提高程序的健壮性。理解和熟练应用这些知识,对于编写出能够正确处理各种文本数据的Java程序是必不可少的。

    【IT十八掌徐培成】Java基础第10天-01.字节集-编解码操作-中繁体-StringBuffer.zip

    徐培成老师的"IT十八掌"系列课程深入浅出地讲解了这些概念,帮助初学者建立坚实的Java根基。在这个"Java基础第10天-01.字节集-编解码操作-中繁体-StringBuffer"的课程中,我们将探讨以下几个关键知识点: 1. 字节集...

    编码:隐匿在计算机软硬件背后的语言.(Code).Charles.Petzold.中文版+英文版

    《编码:隐匿在计算机软硬件背后的语言》是著名计算机科学家查尔斯·佩兹洛德(Charles Petzold)撰写的一部经典著作,它深入浅出地揭示了计算机科学的基础和核心概念。这本书中文版与英文版的结合,为读者提供了更...

    《编码---隐匿在计算机软硬件背后的语言.上》 高清 PDF

    《编码---隐匿在计算机软硬件背后的语言》是程序员领域内一本备受推崇的经典著作,它深入浅出地揭示了计算机科学的基本原理,帮助读者理解计算机系统如何处理信息。这本书的上册主要涵盖了从二进制到高级编程语言的...

    编码的奥秘.pdf下载

    总之,《编码的奥秘》是一本深入浅出的书籍,它将带你走进计算机内部的世界,探索代码如何变成可执行的过程。通过阅读这本书,初学者可以更全面地理解编程的本质,为未来的学习和职业生涯打下坚实的基础。无论是对...

    java网络编程源码

    孙卫琴的《java网络编程》一书深入浅出地讲解了这些核心概念,并提供了丰富的示例代码。以下是基于该书源码的一些关键知识点的详细解析: 1. **Java Socket编程**:Java通过Socket类实现了TCP/IP通信,Socket是网络...

    IBM的java IO教程

    IBM的Java IO教程深入浅出地介绍了Java IO的基本概念和用法,包括流的概念、缓冲区、字符编码、过滤流以及对象的序列化和反序列化。流是IO系统的核心,分为字节流和字符流,其中字节流处理的是8位的字节,而字符流...

    Java加密技术(技巧与实例) pdf.rar

    本书“Java加密技术(技巧与实例)”深入浅出地探讨了这一主题,为开发者提供了丰富的实践指导。以下是一些核心知识点的详细说明: 1. **BASE64编码**:BASE64是一种用于在不支持二进制数据的环境下传输或存储原始...

    浅谈为什么java命令运行class文件出现异常共7页.p

    - **编码问题**:源代码或资源文件的编码与JVM的默认编码不匹配,可能导致字符乱码或加载失败。 - **异常处理**:在程序中未捕获到的异常会抛出,需要检查代码中的try-catch块是否完整。 - **内存溢出**:如果...

    黑马程序员_毕向东_Java基础视频教程第18天-11-IO流(文本文件读取方式二).zip

    毕向东老师的讲解深入浅出,适合初学者和有一定基础的学习者。 通过这个教程,学习者将掌握使用`Scanner`类读取文本文件的方法,以及如何在Java中进行基本的IO操作。结合实践,将有助于巩固和提升Java编程技能。

    TCP-JAVA网络编程(PPT)

    本课程通过PPT的形式深入浅出地讲解了这一主题。以下将详细阐述TCP和Java在网络编程中的核心概念、关键技术和实际应用。 1. **TCP协议基础** - **传输控制协议(TCP)**:TCP是一种面向连接的、可靠的、基于字节流...

    java零基础学习计划.pdf

    多线程是构建并发应用程序的关键,通过深入浅出的讲解,学习者将理解如何创建、启动和停止线程,以及如何正确地编写多线程程序。Java Socket编程是实现网络通信的基础,学习者将了解到如何利用Socket进行数据传输,...

    practical java

    《实用Java》是一份深入浅出的Java编程学习资料,结合了理论与实践,旨在帮助开发者全面理解并熟练掌握Java语言。文档包含了丰富的实例代码,让读者能够在实践中学习和巩固Java编程技能。以下是对这份资源中可能涵盖...

    Java 2 教程(第五版)(PDG)

    《Java 2 教程(第五版)(PDG)》是一部经典的Java编程学习资源,它深入浅出地讲解了Java语言的核心概念和技术。本教程针对的是Java 2平台,即J2SE(Java 2 Standard Edition),是Java开发者的必备参考书籍。下面,...

    java2实用教程(第3版)课件.rar

    《Java2实用教程》是Java编程领域的一本经典教材,主要针对初学者和有一定基础的开发者,旨在深入浅出地介绍Java编程语言的核心概念和技术。第三版的更新通常会涵盖最新的Java版本特性,以适应技术的快速发展。这个...

    数据结构与算法分析(Java版英文

    《数据结构与算法分析》是计算机科学领域的一本经典著作,尤其在Java版本中,它深入浅出地探讨了如何用Java语言实现各种数据结构和算法。这本书对于学习和理解编程基础,提升软件开发能力至关重要。 一、数据结构 ...

    面向对象程序设计Java听课笔记

    浙江大学翁恺老师的Java视频课程深入浅出地讲解了这一主题,这些笔记旨在帮助我们回顾和巩固Java开发的关键知识。 1. 面向对象编程基础 - 类与对象:Java中的基本单元是类,它是具有属性(字段)和行为(方法)的...

Global site tag (gtag.js) - Google Analytics