`
liyong1989
  • 浏览: 9956 次
  • 性别: Icon_minigender_1
  • 来自: 江西
文章分类
社区版块
存档分类
最新评论
阅读更多
字符编码原理
关键字: 基础话 题
编码字符集与乱码问题根源之所在
本文介绍了编码字符集的概念以及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内核是unicode的。但Java总是根据操作系统的默认编码字符集来决定字符串的初始编码,而且Java系统的输入和输出的都是采取操作系统的默认编码,而数据库、文件、网络传输中的字节流……采用的编码更是各不相同。所以不可避免的就会出现烦人的乱码问题了。

解决办法
  1、GB2312、GBK、Unicode(UTF8)?
  从字符集的大小比较 GB2312 < GBK < UTF8,很显然,如果我们采用UTF8作为系统编码的话,是不会有错的。而且如果你要考虑国际化的话,UTF8似乎是你唯一的选择
  2、开发和编译代码时指定字符集为UTF-8
  JBuilder和Eclipse都可以在项目属性中设置。
  3、使用过滤器
  编写过滤器
package com.javer.test.language;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class EncodingFilter
implements Filter
{
FilterConfig config;

public void init(FilterConfig parm1)
throws javax.servlet.ServletException
{
this.config = parm1;
}

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws java.io.IOException, javax.servlet.ServletException
{
if (req.getCharacterEncoding() == null || !req.getCharacterEncoding().equals("UTF-8"))
{
req.setCharacterEncoding("UTF-8");
}
chain.doFilter(req, res);
}

public void destroy()
{
this.config = null;
}
}
  在web.xml文件中配置该过滤器
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>javer‘s project</display-name>
<filter>
<filter-name>EncodingFilter</filter-name>
<display-name>EncodingFilter</display-name>
<description>对编码进行转换</description>
<filter-class>com.javer.test.language.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
  4、在JSP 中进行声明
  在JSP头部声明<%@ page contentType="text/html;charset= UTF-8" %>
  在Jsp的html代码中声明<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5、数据库管理
  一般数据库都可以通过管理设置设定UTF-8
  也可以通过jdbc链接时指定编码参数,如:mysql:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
  6、其它
  其他所有和外界交互时能够设定编码时就设定UTF-8,例如读取文件,操作XML等。总之,记住一个原则:在所有系统的出入口处都用UTF8编码进行“翻译”!
 
分享到:
评论

相关推荐

    Java字符编码原理.pdf

    Java字符编码原理主要涉及到字符集、编码转换以及在开发环境中如何正确配置编码格式,以确保程序处理各种语言,特别是中文时的正确性。在Java中,字符编码涉及到以下几个关键概念: 1. **字符集(Charset)**:字符...

    Java字符编码原理[参照].pdf

    Java字符编码原理主要涉及到字符集、编码转换以及在开发环境中如何确保正确的编码设置。Java作为跨平台的语言,处理字符编码的方式对于确保程序的兼容性和国际化至关重要。以下是对标题和描述中涉及的知识点的详细...

    中英文字符编码查询工具

    本工具“中英文字符编码查询工具”专注于帮助用户快速查询中文和英文字符的编码,这对于开发者调试程序、处理文本数据或理解字符编码原理具有重要意义。 首先,我们来了解最基本的ASCII编码。ASCII(American ...

    Java字符编码原理(动力节点Java学院整理)

    Java字符编码原理是Java开发中不可或缺的基础知识,尤其是在处理涉及多语言环境的系统时,对字符编码的理解至关重要。本文将深入探讨Java中的字符编码过程,帮助开发者解决常见的乱码问题。 首先,Java源代码文件...

    ut8字符编码查询.rar

    总的来说,这个“ut8字符编码查询.rar”压缩包提供的工具和相关的知识点,对于深入理解字符编码原理,以及在实际工作中解决相关问题具有很高的价值。无论是编程、数据分析还是网站开发,掌握字符编码的相关知识都将...

    字符编码详解

    无论是对于软件开发人员还是系统架构师,深入了解字符编码原理,掌握常见编码标准及其转换机制,都是提升软件质量、用户体验的重要保障。通过本文的介绍,希望能够帮助读者建立起清晰的编码概念框架,为今后的工作...

    中英文字符编码查询_V1.1

    这对于开发者调试程序、解决字符编码问题或者理解字符编码原理非常有帮助。 在实际应用中,字符编码的问题可能会导致乱码,特别是在跨平台、跨语言的环境下。因此,了解并正确使用字符编码是每个IT从业者必备的基础...

    计算机字符编码.pdf

    计算机字符编码是计算机处理文本和符号的关键技术,它涉及到如何将各种字符转换成计算机可识别的二进制形式。...理解字符编码原理对于解决乱码问题、进行跨平台文件交换以及开发多语言软件至关重要。

    Win32字符编码

    ### Win32字符编码原理及应用 #### 一、引言 在开发基于Windows平台的应用程序时,理解和掌握字符编码是非常关键的一步。本文将详细介绍Win32 API中所涉及的字符编码,包括ASCII、DBCS(双字节字符集)、Unicode等...

    字符编码查询工具

    在本文中,我们将深入探讨字符编码查询工具,包括它的功能、工作原理以及如何使用。 首先,"字符编码查询工具"正如其标题所示,是一种专门用于查询字符编码的软件。它支持中文字符以及ASCII和GBK这两种常见的字符...

    中英文字符编码查询_V1.1.zip

    这对于开发者调试程序、理解字符处理逻辑或者学习字符编码原理非常有帮助。通过这个工具,用户可以直观地了解字符与数字之间的对应关系,加深对字符编码的理解。 在实际应用中,ASCII编码常用于网络通信、编程语言...

    LTDC—液晶显示中英文(第1节) —字符编码介绍.pptx

    解决这些问题通常需要理解字符编码原理,选择合适的编码格式,并确保数据在传输和处理过程中保持一致。 此外,开发过程中,可以参考相关的技术文档,如《零死角玩转STM32》中的"LTDC—液晶显示中英文"章节,或者...

    Python字符编码_中文乱码.pdf

    本文将深入探讨Python中的字符编码原理,并提供解决中文乱码问题的有效方法。 #### 二、系统默认编码 Python程序在运行过程中,其输出结果与操作系统的默认编码密切相关。不同操作系统有不同的默认编码: 1. **...

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

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

    字符编码解决方案

    本文主要针对C++编程中遇到的乱码问题,从编码原理、Unicode的意义出发,详细讲解了四个典型场景下的乱码问题及其解决方案。 首先,我们要理解编码的基本概念。在计算机中,所有的信息都是由二进制表示的,而字符...

    字符编码转换处理工具

    字符编码是计算机科学中的一个重要概念,它涉及到如何在数字系统中表示和处理文本。在我们的日常生活中,无论是...通过使用这个工具,你可以更深入地了解字符编码背后的原理,提高你在处理文本数据时的效率和精确度。

    字符编码解码工具字符编码,解码

    下面将详细介绍字符编码与解码的基本原理和常见编码格式。 字符编码的历史可以追溯到早期的计算机时代,那时的编码系统相对简单,如美国信息交换标准代码(ASCII)。ASCII编码使用7位二进制数来表示128个不同的字符...

    JAVA 转换字符编码工具

    总的来说,"JAVA 转换字符编码工具"这个主题涵盖了字符编码的基本原理、Java中的字符编码API以及在实际项目中如何处理字符编码问题。`ReadFile.java`这个工具可能就是解决这类问题的一个实例,通过指定和转换字符...

    字符编码的奥秘

    本文将探讨字符编码的历史、原理以及不同编码系统,包括ASCII、扩展ASCII和ISO8859。 首先,我们要理解字符编码的基本概念。字符编码是通过为每个字符分配一个唯一的数字或二进制值来实现的,这样计算机就可以识别...

Global site tag (gtag.js) - Google Analytics