字符集编码以及java乱码问题学习总结

 

字符集和字符编码的概念:

1.字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等
2.
字符编码方式:是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位元组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。对于asciigb2312等字符集,他们在传输和计算机表示时的字节码不用编码,直接用字符对应的字节码表示。但是比如unicode字符集,就有多种不同的编码方式。

单字节字符集:

1.ASCII码。

使用了8位字节中的低7位,总共是127个编码位,它包括了一些控制字符以及一些英文字符,标点符号等。

2.ISO8859系列。

为了弥ASCII系列的不足,出现了很多各种类型的编码,为了避免混乱,ISO组织在1998年之后陆续发表了ISO-8859Latin1 - 西欧字符)系列的编码标准。其中以ISO-8859-1最为典型。ISO-8859-1覆盖了大多数西欧语言,包括:法国、西班牙、葡萄牙、意大利、荷兰、德国、丹麦、瑞典、挪威、芬兰、冰岛、爱尔兰、苏格兰、英格兰等,因而也涉及到了整个美洲大陆、澳大利亚和非洲很多国家的语言。

ISO-8859标准还包括:

I.ISO-8859-2Latin2 -中、东欧字符)

II.ISO-8859-3Latin3 -南欧字符)

III.ISO-8859-4Latin4 -北欧字符)

IV.ISO-8859-5Cyrillic -斯拉夫语)

V.ISO-8859-6Arabic -阿拉伯语)

VIISO-8859-7Greek -希腊语)

VIIISO-8859-8Hebrew -希伯来语)

VIII.ISO-8859-9Latin5

IXISO-8859-10Latin6

XI.ISO-8859-11Thai -泰国语)

XII.ISO-8859-12(保留)

XIII.ISO-8859-13Latin7

XIV.ISO-8859-14Latin8

XV .ISO-8859-15Latin9

ISO 8859系列是互不兼容的,而且,那些欧洲字符(最高位为1的字符),用来在GB2312BIG5中标识双字节汉字编码的首字节。

 

后来计算机到了中国,面对这么多的中文文字,一个字节就表示不过来了,于是多字节编码应运而生:

1.BIG5

包括440个符号,一级汉字5401个、二级汉字7652个,共计13060个汉字。是繁体汉字的市场标准。

2.GB2312-80

全称是《信息交换用汉字编码字符集 基本集》,1980年发布,是中文信息处理的国家标准。GB2312码共收录6763个简体汉字、682个符号,其中汉字部分:一级字3755,以拼音排序,二级字3008,以偏旁排序。ASCIIGB2312GBKGB18030,这些编码方法是向下兼容的(除了18030GBK不兼容)。

3.GBK

汉字内码扩展规范(GBK)是国家技术监督局1994年为Windows 95所制定的新的汉字内码规范,在码位上和GB2312-80兼容。

4.GB18030-2000

全称是《信息交换用汉字编码字符集》,是我国的强制标准,所有不支持GB18030标准的软件将不能作为产品出售。支持中、日、韩(CJK)统一汉字字符和全部CJK统一汉字扩展A的字符,与GB2312编码兼容的

5.UICODE

1984年起,ISO组织就开始研究制定一个全新的标准:通用多八位编码字符集,(Universal Multiple-OctetCoded Character Set),简称UCS,标准的编号为:ISO 10646

统一码(Unicode)是是Universal Code的缩写,是由另一个叫“Unicode学术学会The Unicode Consortium)的机构制定的字符编码系统。UnicodeISO 10646国际编码标准从内容上来说是同步一致的。

1991年,Unicode学术学会与ISO国际标准化组织决定共同制订一套适用于多种语言文本的通用编码标准。UnicodeISO 10646国际编码标准于19921月正式合作发展一套通用编码标准。自此,两个组织便一直紧密合作,同步发展UnicodeISO 10646。国际编码标准。

两者的区别在于:ISO 10646着重定义字符编码,而Unicode则在此基础上,为这些字符及编码数据提出应用的方法以及对语义数据作补充。

UCS的结构UTF-8,UTF-16以及代理对:

UCS的结构是一个四维的编码空间,每一维由一个字节(八位二进制位)组成,范围是00FF。总体上分为128个群组(Group 00-7F),每一群组由256个平面(Plane 00-FF)组成,每一平面有256(Row 00-FF),每一行256个编码位(Cell 00-FF)。所以,每一平面包括65,536个字符位(Character Position0000-FFFF)

整个编码字符集的每个字符都由4个字节,按---的顺序表示。所以UCS的可编码空间为:128 × 256 × 256 × 256 = 231

UCS将其第一个平面(00群组中的00平面)称作基本多语种平面(Basic Multilingual PlaneBMP)。

UCS有两种方式来表示一个字符编码:四字节正规形式(UCS-4Four-octet canonical form)和双字节基本平面形式(UCS-2Two-octet BMP form)。

UCS-4 —— 四字节正规形式

UCS-44个字节来表示一个字符。第一个字节表示组(Group),第二表示平面(Plane),第三表示行(Row),第四表示单元号或列(Cell)。

UCS-2 —— 双字节基本平面形式

当系统只使用BMP的字符码时,可以省略群组和平面中的八位,将字符码由32个位缩短为16个位(2个字节)。标记为UCS-2UCS是定长编码,只能表示BMP中的字符。

UCS只是一个字形和内码上的标准,并没有定义实际在计算机上存取的方法,而UTF便定义了一整套的计算机存取UCS编码的转换格式即UCS Transformation Format。常用UTF-16UTF-8.

UTF-16是用定长16位(2字节)来表示的UCS-2Unicode转换格式。它将Unicode的编码值变成2字节的Big-endian(高位字节在前,低位字节在后)或Little-endian(低位字节在前,高位字节在后)编码。UTF-16利用代理对来访问BMP之外的字符编码。

UCS-4定义了4个字节表示一个字符,但是UCS-2UTF-16只定义了2个字节,代理对的设计就是为了应对这一问题。

UTF-16BMP中开辟了一个特殊的区间(D800- DFFF--代理区,并平分成两个区,分别称为高半代理区(High-half ZoneD800- DBFF),和低半代理区(Low-half ZoneDC00 -DFFF),各有1024个码位。使用时,从高低两个代理区中各取一个编码组成一个四字节的代理,来表示一个在BMP以外平面上的编码字符位。这样一来,总共可以多表示1024×1024个字符,映射到00群组中的0110平面(共16个平面)。所以,一个完好的文本中,高半代理码和低半代理码总是按先后成对出现。

UTF-16UCS-2同样采用16位编码,他们之间的区别是:

UTF-1616Unicode传输协议)是可以对从00x10FFFFUnicode编码区间进行编码的字符编码。它用变长(单个16位表示BMP基本多语种平面,两个16位表示115个平面的UCS字符)的字节数来表示前16USC平面的字符。(UTF-16(16-bitUnicodeTransformationFormat) is acharacter encoding for Unicode capable ofencoding 1,112,064 numbers (called code points) in the Unicodecode spacefrom 0 to 0x10FFFF. It produces avariable-length result of either one ortwo 16-bit code unitsper code point.)

UCS-2是和UTF-16类似的字符编码,但是在19967月份推出的Unicode 2.0标准中是UTF-16的子集。它用定长16字节的格式来表示BMPUCS-4字符。它和UTF-16用单个16字节表示的时候是一样的。(The older UCS-2(2-byteUniversal Character Set) is a similarcharacter encoding that was superseded by UTF-16 in version 2.0 of the Unicodestandard in July 1996. It produces a fixed-length format by simply using thecode point as the 16-bit code unit and produces exactly the same result asUTF-16 for 63,488 code points in the range 0-0xFFFF, including all charactersthat had been assigned a value in this range at that time.)。由于BMP字符涵盖了绝大多数我们日常用到的字符,所以,可以认为,UCS-2UTF-16基本一致。

UTF-8使用了变长技术,在每一个编码区域有不同的字码长度:

1.UCS-2,由1字节至3字节构成;

2.如果UCS-2使用了代理对,则UTF-8最长可到4字节;

3.UCS-4,由1字节至6字节构成。

UTF-8内,

1.如果一个字节,最高位(第8位)为0,表示这是一个ASCII字符(00 - 7F)。可见,所有ASCII编码已经是UTF-8了。

2.如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数,例如:110xxxxx代表它是双字节UTF-8字符的首字节。

3.如果一个字节,以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。

UTF的字节序和BOM

big endianlittle endianbig endianlittle endianCPU处理多字节数的不同方式。例如字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian,将49写在前面,就是little endian

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个Unicode编码是594EUnicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是还是

Unicode规范中推荐的标记字节顺序的方法是BOMBOM不是“Bill Of Material”BOM表,而是Byte Order MarkBOM是一个有点小聪明的想法:

UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFEUCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAKSPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

各种使用UCS2编码的语言,如果要进行相互转换,出现问题可能的原因之一是因为两种语言中编码的字节顺序不一样。

java中的unicode默认使用的是UTF-16BE,windows记事本另存为时候保存的unicode选项其实是UTF-16LE,。如果要选择UTF-16BE,应该选unicode big endian

 

编码和解码几个实验

实验1

“我爱”两个字。

UNICODE编码为:0x6211 0x7231

GBK的编码为:0XCED2 0XB0AE

UTF-8码为E6 88 91 E7 88 B1

默认情况下,在记事本中输入“我爱”,保存后,打开后得到的字节码为CE D2 B0 AE结论:我的系统默认的字符集是GBK。用UNICODE编码另存为,打开后的字节码为:FF FE 11 62 31 72结论: windows记事本另存为时候保存的unicode选项其实是UTF-16LE。在java中使用UNICODEUTF-16编码后输出到文件中,得到的字节码为:FE FF 62 11 72 31结论:java使用的编码字节序是Big-endian。在java中使用UTF8编码后输出到文件中,得到字节码E6 88 91 E7 88 B1。在java中不指定编码方式,得到的字节码是CE D2 B0 AE结论:java在不指定编码方式的时候,使用的是系统的默认字符集编码---GBK

实验二:

把字符串我是好人,以unicode编码方式写入到文本中。

”UTF-16LE”编码读取,输出的结果为:“????”,分析:因为指定了是使用Little-endian,所以unicode编码输出时的big-endian标识FEFF就失去了意义,使用utf-16解码之,变成了无法解析的一个字符“?”。后面四个字符的解析结果则说明,除了“红”和“絙”的字节码在表示上刚好体现为高低位互换的样式,其他几个字符在使用big-endian编码后根据little-endian解码不能得到结果。

而以“UTF-16BE”编码读取,结果则是“?我是好人”,这还是印证了上面的分析,如果在解码时指定了带字节序的编码,则会抛弃字节码中的BOM,而会把这两个字节当成普通字节码来解码,结果出现了乱码。

如果只指定使用unicode编码读取,程序不知道使用的是哪一种字节顺序,先读取开头的FEFF,结果表明是采用的UTF-16BE编码,接下来再继续解析,就得到正确字符。

实验三:

byte [] intstrs = newbyte[]{-50,-46,-80,-82,65,108,105,98,97,98,97};

         Stringstr = new String(intstrs,"ISO8859-1");     

我对newString(“ISO8859-1”)的理解:使用ISO8859-1的编码格式,把每一个byte转换成一个字符,因为以上这些字符都在ISO8859-1的编码范围之内,所以,能够生成一个正常的字符串,那么,字符串对应的编码字节数组也不会改变。然后,根据ISO8859-1编码形成的字符串,转换成unicode的字节编码。这时候,str是一个unicode编码的字符串。再用

String gbkStr = newString(str.getBytes("ISO8859-1"),"GBK");

来进行转换,因为字节数组还是intstrs起先的数值,所以,能够正确把这个字节数组转换成gbk编码。过程是:先把str编码成ISO8859-1的字节码,这一过程,是把11个字符转换成ISO8859-1编码的字节数组,并生成11个字节数组。根据这个字节数组,再使用GBK编码,最终生成GBK编码格式的字符串。

Java操作中可能导致乱码的情况:

1.       编译器编译java源代码的时候,会使用指定的编码把源代码中的字符转换成字节流,windows系统默认使用GBKlinux系统默认使用ISO-8859-1。可以使用javac -encoding GBK MyClass.java指定编码方式。

2.       从文件中读取文本内容,如前所述,如果不指定编码方式,系统会使用当前系统默认的编码方式来解码文本内容,所以正确的方式是指定一个解码的编码方式。

3.       Xml文件的编码方式是定义在xml声明中的,标准的xml解析器总是尝试使用UTF-8方式解码xml文件。可以在xml声明中采用如下的方式:<?xml version="1.0"encoding="GB2312"?>指定编码方式。Xml解析器在解码过程中如果发现字符集之外的字符,会直接报错。此外,当前xml可以指定的中文编码方式只有BGKBIG5

4.       数据库文件存取。需要在数据库中设置存储文本的编码方式,其次,javajdbc驱动也要设置和数据库一致的编码方式才可以正确得读取数据库数据。

5.       模板文件比如velocity模板可以使用input.encoding=GBK配置文件定义读取模板所有的字符编码方式。

 

Jsp-servlet中文汉字编码相关的一些知识:

1.pageEncodingjsp在服务器中需要被编译成servlet,普通java文件在被编译成.class文件时默认都会采用系统默认编码,中文系统默认是GBK,而jsp文件在由jsp编译器编译成java类的时候,会根据jsp文件中指定的编码方式来读取jsp文件。pageEncoding只是告诉jsp编译器当前jsp文件的编码方式jsp编译器会根据这个编码方式读取jsp源文件,生成用UTF-8编码的servlet源文件,。同时,如果设置了pageEncoding,没有在contentType中设置charset,也会根据pageEncoding的值来指定对服务器响应进行重新编码的编码,而pageEncoding不指定编码,jsp编译器会根据contentType中设定的编码读取jsp文件,如果两个都没有,则采用ISO-8859-1编码读取文件,并且设置浏览器响应编码的字符集编码

2.contentType="text/html;charset=UTF-8"的作用是指定对服务器响应进行重新编码的编码.在不使用response.setCharacterEncoding方法时,用该参数指定对服务器响应进行重新编码的编码。

3.request.setCharacterEncoding("UTF-8")的作用是设置对客户端请求进行重新编码的编码,该方法用来指定对浏览器发送来的数据进行重新编码(或者称为解码)时,使用的编码。

4.response.setCharacterEncoding("UTF-8")的作用是指定对服务器响应进行重新编码的编码,服务器在将数据发送到浏览器前,对数据进行重新编码时,使用的就是该编码。

5.浏览器接收发送的编解码。response.setCharacterEncoding("UTF-8")的作用是指定对服务器响应进行重新编码的编码。同时,浏览器也是根据这个参数来对其接收到的数据进行重新编码(或者称为解码)。所以无论你在jsp中设置response.setCharacterEncoding("UTF-8")或者response.setCharacterEncoding("GBK"),浏览器均能正确显示中文(前提是你发送到浏览器的数据编码是正确的,比如正确设置了pageEncoding参数等)。一个实验,在JSP中设置response.setCharacterEncoding("UTF-8"),在IE中显示该页面时,在IE的菜单中选择"查看(V)"à"编码(D)"中可以查看到是" UnicodeUTF-8",而在在JSP中设置response.setCharacterEncoding("GBK"),在IE中显示该页面时,在IE的菜单中选择"查看(V)"à"编码(D)"中可以查看到是"简体中文(GB2312"。如果没有设置服务器响应重新编码的编码,浏览器会默认使用ISO-8859-1进行解码。在IE的菜单中选择"查看(V)"à"编码(D)"中可以查看到的是西欧(ISO),这时候,如果jsp文件中包含中文,在浏览器中就不能正确显示了,但是ISO8859-1不会破坏jsp源文件中字节码的内容,依然可以通过选择正确的解码方式解码获取正确的网页,可以在"查看(V)"à"编码(D)"中选择一个合适的字符集编码(和jsp文件的存储编码一致),就可以得到正确编码的网页。浏览器在发送数据时,对URL和参数会进行URL编码,对参数中的中文,浏览器也是使 response.setCharacterEncoding参数来进行URL编码的。以百度和GOOGLE为例,如果你在百度中搜索"汉字",百度会将其编码为"%BA%BA%D7%D6"。而在GOOGLE中搜索"汉字"GOOGLE会将其编码为"%E6%B1%89%E5%AD%97",这是因为百度的response.setCharacterEncoding参数为GBK,而GOOGLEresponse.setCharacterEncoding参数为UTF-8

6.浏览器在接收服务器数据和发送数据到服务器时所使用的编码是相同的,默认情况下均为JSP页面的 response.setCharacterEncoding参数(或者contentTypepageEncoding参数),我们称其为浏览器编码。当然,在IE中可以修改浏览器编码(在IE的菜单中选择"查看(V)"à"编码(D)"中修改),但通常情况下,修改该参数会使原本正确的页面中出现乱码。一个有趣的例子是,在IE中浏览GOOGLE的主页时,将浏览器编码修改为"简体中文(GB2312",此时,页面上的中文会变成乱码,不理它,在文本框中输入"汉字",提交,GOOGLE会将其编码为"%BA%BA%D7%D6",可见,浏览器在对中文进行URL编码时,使用的就是浏览器编码

弄清了浏览器是在接收和发送数据时,是如何对数据进行编解码的了,我们再来看看服务器在接收和发送数据时,是如何对数据进行编解码的。

对于发送数据,服务器按照response.setCharacterEncoding—contentType—pageEncoding的优先顺序,对要发送的数据进行编码。

对于接收数据,要分三种情况。一种是浏览器直接用URL提交的数据,另外两种是用表单的GETPOST方式提交的数据。

因为各种WEB服务器对这三种方式的处理也不相同,所以我们以Tomcat5.0为例。

无论使用那种方式提交,如果参数中包含中文,浏览器都会使用当前浏览器编码对其进行URL编码。

对于表单中POST方式提交的数据,只要在接收数据的JSP中正确request.setCharacterEncoding参数,即将对客户端请求进行重新编码的编码设置成浏览器编码,就可以保证得到的参数编码正确。那如何得到浏览器编码呢?上面提过了,在默认请情况下,浏览器编码就是你在响应该请求的JSP页面中response.setCharacterEncoding设置的值。所以对于POST表单提交的数据,在获得数据的JSP页面中request.setCharacterEncoding要和生成提交该表单的JSP页面的 response.setCharacterEncoding设置成相同的值。

对于URL提交的数据和表单中GET方式提交的数据,在接收数据的JSP中设置 request.setCharacterEncoding参数是不行的,因为在Tomcat5.0中,默认情况下使用ISO-8859-1URL提交的数据和表单中GET方式提交的数据进行重新编码(解码),而不使用该参数中设定的编码方式对URL提交的数据和表单中GET方式提交的数据进行重新编码(解码)。这就是我们在应用中使用过滤器对请求做编码转换的原因,因为ISO-889-1不会破坏字节码内容,在过滤器中使用ISO-8859-1URL提交的数据或者表单中GET方式提交的数据进行编码,再用浏览器编码,即response.setCharacterEncoding时设置的编码方式来解码,就能得到正确的浏览器提交内容了。

对于同一个应用,最好统一编码,推荐为UTF-8,当然GBK也可以。

你可能感兴趣的:(字符集编码以及java乱码问题学习总结)