`

编码字符集与Java -Java World乱码问题根源之所在

    博客分类:
  • java
阅读更多

摘自:http://www.blogjava.net/ramon/archive/2006/09/24/71505.html  

 

 

本文介绍了编码字符集的概念以及Java与编码字符集之间的关系,文章的内容来自于本人工作过程中的经验积累以及网络中的相关文章介绍,如果文章中有任何纰漏欢迎读者指正,让我们共同讨论学习 J

 

 

1. 字符

字符是抽象的最小文本单位。它没有固定的形状(可能是一个字形),而且没有值。“ A ”是一个字符,“€”(德国、法国和许多其他欧洲国家通用货币的标志)也是一个字符。“中”“国”这是两个汉字字符。字符仅仅代表一个符号,没有任何实际值的意义

2. 字符集

 

字符集是字符的集合。例如,汉字字符是中国人最先发明的字符,在中文、日文、韩文和越南文的书写中使用。这也说明了字符和字符集之间的关系,字符组成字符集。

3. 编码字符集

 

编码字符集是一个字符集(有时候也被简称位字符集),它为每一个字符分配一个唯一数字。最早的编码是 iso8859-1 ,和 ascii 编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码。

 

 

iso8859-1: 属于单字节编码字符集,最多能表示的字符范围是 0-255 ,应用于英文系列,除了 iso8859-1 以外还有其他 iso8859 系列的编码,这些编码都是为了满足欧洲国家语言字符的需要而设计的。  

 

GB2312/GBK/ GB18030: 前面提到的 iso8859-1 最多只能表示 256 个字符,这对于汉字来说实在是有些抱歉,所以就有了现在要介绍的汉字国标码,专门用来表示汉字,是双字节编码字符集,而英文字母和 iso8859-1 一致(兼容 iso8859-1 编码)。其中 GBK 编码能够用来同时表示繁体字和简体字,而 GB2312 只能表示简体字, GBK 是兼容 GB2312 编码的。而 GB18030-2000 则是一个更复杂的字符集,采用变长字节的编码方式,能够支持更多的字符。需要注意的是中国政府要求所有在中国出售的软件必须支持 GB18030  

 

Unicode 这是最统一的编码字符集,可以用来表示所有语言的字符 ,不兼容任何前面提到的编码字符集。 Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“ U+ ”,所以“ A ”的编码书写为“ U+0041 注意:在 JAVA 语言中书写时应该使用转义符‘ \u ’表示,如 char charA = ‘\u0041’; 这种表示方法等与 char charA = ‘A’;

 

ASCII (英文) ==> 西欧文字 ==> 东欧字符集(俄文,希腊语等) ==> 东亚字符集( GB2312 BIG5 SJIS 等) ==> 扩展字符集 GBK GB18030 这个发展过程基本上也反映了字符集标准的发展过程,但这么随着时间的推移,尤其是互联网让跨语言的信息的交互变得越来越多的时候,太多多针对本地语言的编码标准的出现导致一个应用程序的国际化变得成本非常高 。尤其是你要编写一个同时包含法文和简体中文的文档,这时候一般都会想到要是用一个通用的字符集能够显示所有语言的所有文字就好了,而且这样做应用也能够比较方便的国际化,为了达到这个目标,即使应用牺牲一些空间和程序效率也是非常值得的。 UNICODE 就是这样一个通用的解决方案。  

 

4. Unicode 编码字符集

 

Unicode 因为必须将中、韩、日、英、法、阿拉伯……等许多国家所使用的文字都纳入,目前已经包含了六万多个字符,所以 Unicode 使用了 16 个位来为字符编码。因为 Unicode 使用了 16 位编码,所以每个字符都用 16 位来储存或传输是很自然的事,这种储存或传输的格式 称为 UTF-16 (一种 Unicode 的字符编码方案,在这里所说的 UTF-16 并不涉及增补字符的表示,本文将会在稍后介绍)。但是如果你使用到的字符都是西方字符,那么你一定不会想用 UTF-16 的格式,因为体积比 8 位的 iso8859-1 多了一倍,如此一来就必须考虑程序运行时各种字符在内存中所占空间的性能问题,这便引入了字符编码方案的概念:

字符编码方案是从一个或多个编码字符集到一个或多个固定宽度代码单元序列的映射。

 

最常用的代码单元是字节,所以可以简单的认为字符编码方案是为了告诉计算机如何将编码字符集(如 Unicode )映射到计算机可以识别的数据格式中,如字节。这种编码方案往往能够为他所对应的字符集在计算机处理时提供更为优化的空间以及性能上的解决方案。 Unicode 编码字符集有三种字符编码方案,下面将逐一介绍:  

 

l         UTF-32* 即将每一个 Unicode 编码表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。显而易见,对于英文字母的表示将需要多个 0 字节,仅仅因为我们需要 4 个字节 32 位来表示一个 Unicode 字符。

 

l         UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 编码进行编码。值 U+0000 U+FFFF 编码为一个相同值的 16 位单元。增补字符 * 编码为两个代码单元,第一个单元来自于高代理范围( U+D800 U+DBFF ),第二个单元来自于低代理范围( U+DC00 U+DFFF )。这在概念上可能看起来类似于多字节编码,但是其中有一个重要区别:值 U+D800 U+DFFF 保留用于 UTF-16 ;没有这些值分配字符作为代码点。这意味着,对于一个字符串中的每个单独的代码单元,软件可以识别是否该代码单元表示某个单单元字符,或者是否该代码单元是某个双单元字符的第一个或第二单元。这相当于某些传统的多字节字符编码来说是一个显著的改进,在传统的多字节字符编码中,字节值 0x41 既可能表示字母“ A ”,也可能是一个双字节字符的第二个字节。  

 

l         UTF-8 使用一至四个字节的序列对编码 Unicode 进行编码。 U+0000 U+007F 使用一个字节编码, U+0080 U+07FF 使用两个字节, U+0800 U+FFFF 使用三个字节,而 U+10000 U+10FFFF 使用四个字节。 UTF-8 设计原理为:字节值 0x00 0x7F 始终表示代码点 U+0000 U+007F Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他 Unicode 编码字符,这一特性使 UTF-8 可以很方便地在软件中将特殊的含义赋予某些 ASCII 字符。 UTF-8 的格式在编码英文时,只需要 8 位,但是中文则是 24 位,其他更加偏僻的字符才又可能是 32 位,这也是 UTF-8 最大的编码特点,可以最高效率的利用计算机空间,因为在计算机处理的时候大多数情况下还是只使用英文进行运算和处理,这也是为什么还需要 UTF-8 的主要原因,因为毕竟互联网 70 %以上的信息仍然是英文。如果连英文都用 2 个字节存取 (UCS-2) ,空间浪费不就太多了?    

 

*   UTF­-32 表示 Unicode Transformation Form 32-bit form UTF-16 UTF-8 依此类推。

 

*   Unicode 最初设计是作为一种固定宽度的 16 位字符编码。在 Java 编程语言中,基本数据类型 char 初衷是通过提供一种简单的、能够包含任何字符的数据类型来充分利用这种设计的优点。不过,现在看来, 16 位编码的所有 65,536 个字符并不能完全表示全世界所有正在使用或曾经使用的字符。于是, Unicode 标准已扩展到包含多达 1,112,064 个字符。那些超出原来的 16 位限制的字符被称作增补字符。

5. Java 与编码字符集

 

从上面的介绍我们知道了 Unicode 编码字符集可以用来表示世界上所有的语言文字。 Java 内部处理字符使用的字序方式是 Unicode ,这是一种通行全球的编码方式,他使 Java 语言能够描述世界上所有的文字。在 Java 程序中对各种字符在内存中处理是使用 Unicode UTF-8 编码方式,这也是因为 UTF-8 的特点所决定的, Class File (也就是 bytecode )中有一栏位叫做常数区( Constant Pool ),一律使用 UTF-8 为子元编码。

 

这看起来一切正常, Java 可以处理世界上所有的字符,一切都是按照秩序在运行,但是,从前面的讨论我们知道,世界上并不是仅仅只有 Unicode 编码字符集,同时存在的还有 iso8859-1 GBK 等编码字符集,就是在 Unicode 中也同样存在着 UTF-8 UTF-16 UTF-32 等多种编码,如果传入的字节编码采用的是 GB18030 ,而采用的解码方式为 UTF-8 那会有什么后果呢,看看下面的代码片段:  

public static final String TEST_RESOURCE = " 你好 ";      

public static void testEncoding() {        

    try {            

        byte[] bytes = TEST_RESOURCE.getBytes("GB18030");            

        String result = new String(bytes, " UTF-8 ");            

        System.out.println("Receive value: [" + result + "].");        

    } catch (UnsupportedEncodingException e) {            

        / / TODO Auto-generated catch block            

        e.printStackTrace();        

    }    

}

执行以上的代码片段,在我的机器( Win XP 中文版)上面得到的结果是:

Receive value: [���].

明白了吧,这就是久负盛名的乱码问题的根源,目前在市面上存在有多种编码字符集,以及编码字符集的编码方案,所以虽然在 Java 中内部是以 Unicode UTF-8 来处理各种字符的表示以及运算,但是这仅仅是在 Java 内部而以,如果 Java 程序需要和外部应用系统进行交互,比如与操作系统,数据库系统之间的交互,那么在这些交互过程中如何处理字符集的编码解码是解决好 Java 应用程序乱码问题的根源。 如果将上面的代码块修改成如下的代码块:    

public static void testEncoding() {        

    try {            

        byte[] bytes = TEST_RESOURCE.getBytes("GB18030");            

        String result = new String(bytes, " GB18030 ");            

        System.out.println("Receive value: [" + result + "].");        

    } catch (UnsupportedEncodingException e) {            

        // TODO Auto-generated catch block            

        e.printStackTrace();        

    }    

}

 

注意红色标注的地方,执行以上的代码块将会受到预期的结果:

Receive value: [ 你好 ].

 

统一字符的编码类型和解码类型 ,如此一来任何乱码问题都不会再是问题了。在网上可以搜索到 N 多的关于如何解决 J2EE 乱码问题的文章,我在这里也就不废话了,我只是想说说 Java 乱码问题的根源之所在。

 

如果你仔细想想 Java 的开发过程,原文件编写、 javac 编译、 java 执行,这每一步骤都会涉及到编码的转换过程,这个过程总是存在的,只是有的时候用默认的参数进行。

 

我们从 javac 这个命令来开始我们的分析,编译的时候,如果你不说明源文件编码方式的话, javac 编译器在读进此原始程序文件开始编译之前,会先去询问操作系统档案预设的编码方式为何。以我的操作系统 WIN XP 中文版来说, javac 会先询问 WIN XP ,得知当前的编码是用 GB18030 的方式编码。然后就可以将源文件由 GB18030 转成 Unicode 编码方式,开始进行编译。在这里就会发生一下一些编码问题:

 

 

l         如果操作系统的国籍资料设定错误,会造成 javac 编译器取得的编码信息是错误的,这里也有可能由于系统属性 file.encoding 设置错误,在我的系统中该属性为 GB18030 ,可以通过代码 System.out.println(System.getProperties()); 输出可能的系统属性。  

 

l         较差劲的编译器可能没有主动询问操作系统的编码方式,而是采用编译器预设的编码方式,当然这种情况对于目前先进的编译器来说已经不存在了,但是这确实是一种可能的原因。

 

 

l         源代码是在英文操作系统上书写采用编码 iso8859-1 ,写好以后再将源代码传递给中文操作系统进行编译,这样由于两个操作系统的编码方式不同,也会造成 javac 执行错误。  

 

明白了吧,这些问题在我们日常的代码编写过程中,往往由于默认的属性都正好能满足我们的需要,即源代码的书写以及编译都采用操作系统默认的编码方式,所以可能很多人到目前为止都没有遇见过诸如此类的问题,但是我们要知道,这些问题确实是存在的。

 

Java 编译器在执行过程中给我们提供了可选的 encoding 参数来告诉编译器该采用何种编码方式将读入的源文件转换成 Unicode 编码方式,然后再进行后续的编译工作。

javac –encoding GB18030 ….  

 

6. Inside

OK ,通过前面的介绍希望读者能够对 Java 以及各种字符编码之间的关系有个简单的了解,下面我在继续总结一下:

l         Javac 是以系统默认编码( file.encoding 系统属性)读入源文件,然后按 Unicode 进行编码的。  

l         JAVA 运行的时候, JAVA 也是采用 Unicode 编码的,为了高度利用内存空间提高效率对 Unicode 字符编码采用了 UTF-8 的方式编码,并且默认输入和输出的都是操作系统的默认编码。  

l         也就是说在 new String(bytes,encode) 中,系统认为输入的是编码为 encode 的字节流,换句话说,如果按 encode 来翻译 bytes 才能得到正确的结果;而在 new String(bytes) 中采用的就是根据 file.encoding 系统属性读入的编码方式来进行编码,同样也必须根据系统默认的编码才能得到正确的结果,这个结果最后要在 JAVA 中保存,它还是要从这个 encode 转换成 Unicode ,因为在 JAVA 中各种字符均是以 Unicode 的形式来处理的。  

l         也就是说有 bytes-->encode 字符 -->Unicode 字符的转换;而在 String.getBytes([encode]) 中,系统要做一个 Unicode 字符 -->encode 字符 -->bytes 的转换。  

 

希望通过本文的介绍能够使你对字符集编码的概念以及 Java 与字符集编码之间的关系有个清楚的认识,我相信,如果搞清楚了他们之间的关系,那个在 Java world 鼎鼎有名的乱码问题将一去不再复返了 J

 

分享到:
评论

相关推荐

    java中文乱码问题详解--- java中文乱码问题详解

    **网络传输中的编码问题**:在网络环境中,如Web应用程序中,客户端发送的数据和服务器端接收的数据可能存在编码不一致的情况,尤其是在处理HTTP请求时,若没有明确指定字符集,浏览器通常会默认使用UTF-8编码进行...

    java字符集编码问题

    本文旨在深入探讨与Java相关的字符集编码知识,包括但不限于编码的基本概念、几种常见的字符集编码类型及其特点,以及Java如何处理这些字符集编码问题。 #### 二、编码基本知识 1. **ISO 8859-1** ISO 8859-1是...

    有关pdfbox-1.3.1中Identity-H编码为乱码的解决方法

    在版本1.3.1中,用户可能会遇到使用Identity-H编码时出现乱码的问题。Identity-H编码是一种无损的Unicode编码方式,它允许PDF文档包含任何Unicode字符,但如果没有正确处理,就可能导致中文、日文或其他非ASCII字符...

    JAVA及相关字符集编码问题

    在深入探讨JAVA与字符集编码问题之前,我们首先需要理解不同字符集编码的基本概念以及它们在JAVA环境中的应用。字符集编码是计算机系统中表示文字的一种方式,它决定了如何将字符转换为二进制数据,以便于存储和传输...

    Java 所有字符串转UTF-8 万能工具类-GetEncode.java

    不需要关心接受的字符串编码是UTF_8还是GBK,还是ios-8859-1,自动转换为utf-8编码格式,无需判断字符串原有编码,用法://处理编码String newStr = GetEncode.transcode(oldStr);

    Java字符集和编码

    ### Java字符集和编码 ...总之,Java中的字符集和编码是解决国际化应用中乱码问题的关键技术之一。通过对这些概念的理解和掌握,可以帮助开发者更好地处理文本数据,提高应用程序的兼容性和稳定性。

    字符集--Java编码相关.doc

    Java字符集与编码问题在编程实践中经常遇到,尤其是在处理中文字符时。Java系统内部以UTF-8编码进行字符串运算,但字符串的初始编码则取决于操作系统的默认编码。这意味着,如果Java程序的输入、输出以及操作系统三...

    java压缩zip文件解决中文乱码问题

    在Java中,`java.util.zip`包提供了对ZIP文件的基本操作,但默认使用的是平台默认的字符集,这可能在跨平台操作时引发乱码问题。 为了解决这个中文乱码问题,我们需要在创建ZipEntry时指定合适的字符集,通常是UTF-...

    java读写csv文件,中文乱码问题

    - Java库如OpenCSV、Apache Commons CSV或牛顿CSV库提供了方便的方法来处理CSV文件,它们会自动处理编码问题。 - 使用这些库时,需要配置正确的编码。例如,使用Apache Commons CSV: ```java Reader reader = ...

    JAVA字符编码:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换

    ### JAVA字符编码详解:Unicode, ISO-8859-1, GBK, UTF-8 及其相互转换 #### 一、引言 在Java编程中,字符编码的管理和转换是一项基本而又重要的任务。不同的编码标准适用于不同的场景,而理解和掌握这些编码之间...

    jdbc连接oracle字符集不同出现乱码

    综上所述,当使用 JDBC 连接 Oracle 数据库并遇到字符集不同导致的乱码问题时,可以通过调整 SQL 语句的编码或结果集的编码来解决。这两种方法都可以有效地避免乱码问题的发生,但在实际应用中应根据具体情况选择最...

    Java避免UTF-8的csv文件打开中文出现乱码的方法

    然而,在Java中读取和写入csv文件时,中文字符如果不正确地处理,可能会出现乱码的情况。下面我们将详细介绍Java避免UTF-8的csv文件打开中文出现乱码的方法。 首先,需要了解UTF-8和UTF-16LE这两种编码格式。UTF-8...

    深入理解字符编码(字符集 字符编码 字符显示 乱码问题)

    文档中主要介绍了各类字符集以及相关的字符编码,字符的显示原理,从输入到显现的整个过程,程序中出现的乱码问题以及解决方案

    关于GBK和Unicode字符集转换乱码问题

    乱码问题是字符集转换过程中常见的问题之一,尤其是在GBK与Unicode这种常用但又有所区别的字符集间转换时。通过正确理解字符集的原理、使用适当的工具和技术手段、以及严谨的测试流程,可以有效地避免和解决乱码问题...

    java中文乱码字符集解决大全.pdf

    Java程序在处理中文字符时可能会遇到乱码问题,这主要源于计算机历史上的编码标准差异和Java自身的编码机制。本文将详细分析这个问题的来源、Java编码转换的过程以及解决策略。 1. 中文问题的来源 早期的计算机...

    Java中文乱码浅析及解决方案

    Java 中文乱码问题是一个常见的编程困扰,尤其对于处理中文字符的Java程序而言。这个问题通常源于字符编码的不一致,即不同环节采用的字符编码标准不统一。本文将深入探讨这一问题,并提供相应的解决方案。 首先,...

    Java解决WE8DEC字符集乱码问题

    在IT领域,尤其是在Java编程中,字符编码问题是一个常见的挑战,特别是当涉及到处理不同国家和地区字符集时。WE8DEC是一种Oracle数据库使用的字符集,它主要用于表示西欧语言的字符,包括德语、法语、西班牙语等。...

    MYSQL字符集与乱码问题分析

    《MySQL字符集与乱码问题分析》一文深入探讨了字符编码的历史背景、技术细节以及在MySQL中的应用,尤其关注解决常见的乱码问题。本文将根据提供的内容摘要,详细阐述其中涉及的关键知识点。 ### 字符集背景知识 ##...

    Java中的字符集编码入门(二)编码字符集与字符集编码的区别[参考].pdf

    在Java开发中,处理字符串时,我们需要关注字符集编码问题,特别是在读写文件、网络传输或者数据库操作中。使用不当可能导致乱码问题。Java提供了java.nio.charset包来处理字符编码,例如使用Charset类进行编码和...

Global site tag (gtag.js) - Google Analytics