Java字符编码知识简介

1、基本信息
摘要:在Java应用程序特别是Web应用中,经常遇到字符的编码问题。为了防止出现乱码,首先需要了解字符编码的基本概念以及Java是如何处理字符编码的,这样就可以有目的地在输入/输出环节中增加必要的转码。本文将分以下几部分介绍:
1.   什么是字符集?什么是编码?
2.   常用字符集有哪些?
3.   为什么会有乱码?
4.   Java字符编码
5.   JSP编码
6.   有没有万金油?
7.   参考资料和推荐参阅


2、什么是字符集?什么是编码?
l 字符(Character):是文字与符号的总称,包括文字、图形符号、数学符号等。
l 字符集(Charset):就是一组抽象字符的集合。
       字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。
       一组有共同特征的字符也可以组成字符集,比如繁体汉字字符集、日文汉字字符集。
       字符集的子集也是字符集。
计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符
l 编码(Encoding):
       制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。
       每种编码都限定了一个明确的字符集合,叫做被编码过的字符集(Coded Character Set),这是字符集的另外一个含义。通常所说的字符集大多是这个含义。
 
 
3、常用字符集有哪些?
ASCII:
       American Standard Code for Information Interchange,美国信息交换标准码。
       目前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。 它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。 ASCII字符集由控制字符和图形字符组成。 在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1。偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。
ISO 8859-1:
       全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。
       ASCII收录了空格及94个“可印刷字符”,足以给英语使用。但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用ASCII及控制字符以外的区域来储存及表示。除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。
       ISO 8859-1 (Latin-1) - 西欧语言
       很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。 但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。 而且在很多协议上,默认使用该编码。
 
UCS:
       通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。
       UCS包含了已知语言的所有字符。除了拉丁语、希腊语、斯拉夫语、希伯来语、阿拉伯语、亚美尼亚语、格鲁吉亚语,还包括中文、日文、韩文这样的象形文字,UCS还包括大量的图形、印刷、数学、科学符号。
² UCS-2: 与unicode的2byte编码基本一样。
² UCS-4: 4byte编码, 目前是在UCS-2前加上2个全零的byte。
 
Unicode:
       Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。
       Unicode是http://www.unicode.org制定的编码机制, 要将全世界常用文字都函括进去。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。 1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。
       Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。
 
UTF: Unicode 的实现方式不同于编码方式。
       一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
UTF-8:   8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。
UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。
 
汉字编码:
GB2312字集是简体字集,全称为GB2312(80)字集,共包括国标简体汉字6763个。
BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个。
GBK字集是简繁字集,包括了GB字集、BIG5字集和一些符号,共包括21003个字符。
GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大一统”的标准。
 
ANSI和Unicode big endia:
       我们在Windows系统中保存文本文件时通常可以选择编码为ANSI、Unicode、Unicode big endian和UTF-8,这里的ANSI和Unicode big endia是什么编码呢?
 ANSI:使用2个字节来代表一个字符的各种汉字延伸编码方式,称为ANSI编码。 在简体中文系统下,ANSI编码代表GB2312编码,在日文操作系统下,ANSI编码代表JIS编码。
 Unicode big endia: UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。 Unicode规范中推荐的标记字节顺序的方法是BOM(即Byte Order Mark)。 在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。 UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。 这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。 因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。 Windows就是使用BOM来标记文本文件的编码方式的。
4、为什么会乱码?
       乱码是个老问题,字符在保存时的编码格式如果和要显示的编码格式不一样的话,就会出现乱码问题。在我们的Web应用中,从底层数据库编码、Web应用程序编码到HTML页面编码,如果有一项不一致的话,就会出现乱码。 所以,解决乱码问题说难也难说简单也简单,关键是让交互系统之间编码一致。
5、Java字符编码
       Java在运行期一律以unicode来存储字符,这样有利的支持了多语言环境。
       Java在读取文件的时候默认是按照系统默认语言(字符集)编码来解码文件,读取和保存时候的编码不一致也导致程序中参数值错误,用FileInputStream类读取文件可以指定编码读取。
       Java在输出到系统显示时,会把内存中变量字符再通过系统默认语言(字符集)编码去转换,所以在输出过程中也会碰到一系列的编码问题。
 首先要了解JAVA处理字符的原理。JAVA使用UNICODE来存储字符数据,处理字符时通常有三个步骤:
  - 按指定的字符编码形式,从源输入流中读取字符数据
  - 以UNICODE编码形式将字符数据存储在内存中
  - 按指定的字符编码形式,将字符数据编码并写入目的输出流中。
  所以JAVA处理字符时总是经过了两次编码转换,一次是从指定编码转换为UNICODE编码,一次是从UNICODE编码转换为指定编码。如果在读入时用 错误的形式解码字符,则内存存储的是错误的UNICODE字符。而从最初文件中读出的字符数据,到最终在屏幕终端显示这些字符,期间经过了应用程序的多次 转换。如果中间某次字符处理,用错误的编码方式解码了从输入流读取的字符数据,或用错误的编码方式将字符写入输出流,则下一个字符数据的接收者就会编解码 出错,从而导致最终显示乱码。
  这一点,是我们分析字符编码问题以及解决问题的指导思想。
  好,现在我们开始一只只的解决这些乱码怪兽。
  一、在JAVA文件中硬编码中文字符,在eclipse中运行,控制台输出了乱码。
  例如,我们在JAVA文件中写入以下代码:
  String text = "大家好";
  System.out.println(text);
  如果我们是在eclipse里编译运行,可能看到的结果是类似这样的乱码:????。那么,这是为什么呢?
  我们先来看看整个字符的转换过程。
  1. 在eclipse窗口中输入中文字符,并保存成UTF-8的JAVA文件。这里发生了多次字符编码转换。不过因为我们相信eclipse的正确性,所以我们不用分析其中的过程,只需要相信保存下的JAVA文件确实是UTF-8格式。
  2. 在eclipse中编译运行此JAVA文件。这里有必要详细分析一下编译和运行时的字符编码转换。
  - 编译:我们用javac编译JAVA文件时,javac不会智能到猜出你所要编译的文件是什么编码类型的,所以它需要指定读取文件所用的编码类型。默认 javac使用平台缺省的字符编码类型来解析JAVA文件。平台缺省编码是操作系统决定的,我们使用的是中文操作系统,语言区域设置通常都是中国大陆,所 以平台缺省编码类型通常是GBK。这个编码类型我们可以在JAVA中使用System.getProperty("file.encoding")来查 看。所以javac会默认使用GBK来解析JAVA文件。如果我们要改变javac所用的编码类型,就要加上-encoding参数,如javac -encoding utf-8 Test.java。
  这里要另外提一下的是eclipse使用的是内置的编译器,并不能添加参数,如果要为javac添加参数则建议使用ANT来编译。不过这并非出现乱码的原因,因为eclipse可以为每个JAVA文件设置字符编码类型,而内置编译器会根据此设置来编译JAVA文件。
  - 运行:编译后字符数据会以UNICODE格式存入字节码文件中。然后eclipse会调用java命令来运行此字节码文件。因为字节码中的字符总是 UNICODE格式,所以java读取字节码文件并没有编码转换过程。虚拟机读取文件后,字符数据便以UNICODE格式存储在内存中了。
  3. 调用System.out.println来输出字符。这里又发生了字符编码转换。
  System.out.println使用了PrintStream类来输出字符数据至控制台。PrintStream会使用平台缺省的编码方式来输出字 符。我们的中文系统上缺省方式为GBK,所以内存中的UNICODE字符被转码成了GBK格式,并送到了操作系统的输出服务中。因为我们操作系统是中文系 统,所以往终端显示设备上打印字符时使用的也是GBK编码。如果到这一步,我们的字符其实不再是GBK编码的话,终端就会显示出乱码。
  那么,在eclipse运行带中文字符的JAVA文件,控制台显示了乱码,是在哪一步转换错误呢?我们一步步来分析。
  - 保存JAVA文件成UTF-8后,如果再次打开你没有看到乱码,说明这步是正确的。
  - 用eclipse本身来编译运行JAVA文件,应该没有问题。
  - System.out.println会把内存中正确的UNICODE字符编码成GBK,然后发到eclipse的控制台去。等等,我们看到在Run Configuration对话框的Common标签里,控制台的字符编码被设置成了UTF-8!问题就在这里。System.out.println已经把字符编码成了GBK,而控制台仍然以UTF-8的格式读取字符,自然会出现乱码。

  将控制台的字符编码设置为GBK,乱码问题解决。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

(个人认为:)此例子举的一点都不好,因为如果common设置成utf-8,输出的只要是中文肯定是乱码!

  (这里补充一点:eclipse的控制台编码是继承了workspace的设置的,通常控制台编码里没有GBK的选项而且不能输入。我们可以先在 workspace的编码设置中输入GBK,然后在控制台的设置中就可以看到GBK的选项了,设置好后再把workspace的字符编码设置改回utf- 8就是。)
6、编码JSP
       这里我们主要是介绍JSP页面的两个重要属性:pageEncoding、contentType、charset
       pageEncoding是jsp文件本身的编码
       contentType的charset是指服务器发送给客户端时的内容编码
       charset有两个作用:一是JSP文件的编码方式:在读取JSP文件、生成JAVA类时,源JSP文件中汉字的编码;二是JSP输出流的编码方式:在执行JSP时,往response流里面写入数据的编码方式
       当应用服务器将JSP编译成.java文件时,会根据pageEncoding的设定读取jsp,结果是由指定的编码方案翻译成统一的UTF-8 JAVA源码(即.java),如果pageEncoding设定错了,或没有设定,出来的就是中文乱码。
       当应用服务器利用已经编译为二进制的JSP(.class)输出页面时,contentType这时则决定了输出页面的编码。
       jsp文件不像.java,.java在被编译器读入的时候默认采用的是操作系统所设定的locale所对应的编码,比如中国大陆就是GBK,台湾就是BIG5或者MS950。而一般我们不管是在记事本还是在ue中写代码,如果没有经过特别转码的话,写出来的都是本地编码格式的内容。所以编译器采用的方法刚好可以让虚拟机得到正确的资料。
但是jsp文件不是这样,它没有这个默认转码过程,但是指定了pageEncoding就可以实现正确转码了。
举个例子:
<%@ page contentType="text/html;charset=utf-8" %>
大都会打印出乱码,因为我输入的“你好吗”是gbk的,但是服务器是否正确抓到“你好吗”不得而知。
但是如果更改为
<%@ page contentType="text/html;charset=utf-8" pageEncoding="GBK"%>
这样就服务器一定会是正确抓到“你好吗”了。
7、有没有万金油?
       J2EE应用程序是运行在J2EE容器中。在这个系统中,输入途径有很多种:一种是通过页面表单打包成请求(request)发往服务器的;第二种是通过数据库读入;还有第3种输入比较复杂,JSP在第一次运行时总是被编译成Servlet,JSP中常常包含中文字符,那么编译使用javac时,Java将根据默认的操作系统编码作为初始编码。除非特别指定,如在Jbuilder/eclipse中可以指定默认的字符集。
       输出途径也有几种:第一种是JSP页面的输出。由于JSP页面已经被编译成Servlet,那么在输出时,也将根据操作系统的默认编码来选择输出编码,除非指定输出编码方式;还有输出途径是数据库,将字符串输出到数据库。
由此看来,一个J2EE系统的输入输出是非常复杂,而且是动态变化的,而Java是跨平台运行的,在实际编译和运行中,都可能涉及到不同的操作系统,如果任由Java自由根据操作系统来决定输入输出的编码字符集,这将不可控制地出现乱码。
正是由于Java的跨平台特性,使得字符集问题必须由具体系统来统一解决,所以在一个Java应用系统中,解决中文乱码的根本办法是明确指定整个应用系统统一字符集。
指定统一字符集时,到底是指定ISO8859_1 、GBK还是UTF-8呢?
1)如统一指定为ISO8859_1,因为目前大多数软件都是西方人编制的,他们默认的字符集就是ISO8859_1,包括操作系统Linux和数据库MySQL等。这样,如果指定Jive统一编码为ISO8859_1,那么就有下面3个环节必须把握:
开发和编译代码时指定字符集为ISO8859_1。
运行操作系统的默认编码必须是ISO8859_1,如Linux。
在JSP头部声明。
2)如果统一指定为GBK中文字符集,上述3个环节同样需要做到,不同的是只能运行在默认编码为GBK的操作系统,如中文Windows。
统一编码为ISO8859_1和GBK虽然带来编制代码的方便,但是各自只能在相应的操作系统上运行。但是也破坏了Java跨平台运行的优越性,只在一定范围内行得通。例如,为了使得GBK编码在linux上运行,设置Linux编码为GBK。
那么有没有一种除了应用系统以外不需要进行任何附加设置的中文编码根本解决方案呢?
将Java/J2EE系统的统一编码定义为UTF-8。UTF-8编码是一种兼容所有语言的编码方式,惟一比较麻烦的就是要找到应用系统的所有出入口,然后使用UTF-8去“结扎”它。
一个J2EE应用系统需要做下列几步工作:
开发和编译代码时指定字符集为UTF-8。JBuilder和Eclipse都可以在项目属性中设置。
使用过滤器,如果所有请求都经过一个Servlet控制分配器,那么使用Servlet的filter执行语句,将所有来自浏览器的请求(request)转换为UTF-8,因为浏览器发过来的请求包根据浏览器所在的操作系统编码,可能是各种形式编码。关键一句:
request.setCharacterEncoding("UTF-8")
在JSP头部声明:
<%@ page contentType="text/html;charset= UTF-8" %>
在Jsp的html代码中,声明UTF-8:
<META http-equiv="Content-Type" CONTET="text/html; charset=utf-8">
设定数据库连接方式是UTF-8。例如连接MYSQL时配置URL如下:
       jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8

一般数据库都可以通过管理设置设定UTF-8。其他和外界交互时能够设定编码时就设定UTF-8,例如读取文件,操作XML等。

参考资料
       1 http://java.ccidnet.com/art/3559/20070614/1112507_1.html
       2 http://www.jdon.com/idea/chinesejava.htm
       3 http://www.bitscn.com/java/webservice/200611/82116.html
       4 http://www.javaeye.com/article/97803

你可能感兴趣的:(Java字符编码知识简介)