每个程序员都不可避免的遇到字符编码的问题,特别是作Web开发的程序员,“乱码问题”一直是让人头疼的问题,也许您已经不多遇到“乱码”问题, 然而,对解决乱码的方法的内在原理,您是否明白?本人做为一个程序员,在字符编码方面一样遇到很多问题,并且一直对各类编码懵懵懂懂、不清不楚;在工做中也曾经遇到一个很烦人的编码问题。这两天在网上收集了大量编码方面的资料,对字符编码算是理解的比较清楚了。下面把我认为比较重要的知识点记录下来,一方面方便之后复习;另外一方面也但愿给跟我同样懵懵懂懂的人一个参考。不对或不妥之处,请批评指正。html
在此以前,先了解一些有用概念:“字符集”、“字符编码”和“内码”。程序员
字符是各类文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。字符集是多个字符的集合,字符集种类较多,每一个字符集包含的字符 个数不一样,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各类字符集文字,须要进行字符 编码,以便计算机可以识别和存储各类文字。 算法
编码(encoding)和字符集不一样。字符集只是字符的集合,不必定适合做网络传送、处理,有时须经编码(encode)后才能应用。如Unicode可依不一样须要以UTF-八、UTF-1六、UTF-32等方式编码。安全
字符编码就是以二进制的数字来对应字符集的字符。网络
所以,对字符进行编码,是信息交流的技术基础。编辑器
使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫作“字符集”。函数
规定每一个“字符”分别用一个字节仍是多个字节存储,用哪些字节来存储,这个规定就叫作“编码”。工具
各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”通常都是同时制定的。所以,日常咱们所说的“字符集”,好比:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。学习
注意:Unicode字符集有多种编码方式,如UTF-八、UTF-16等;ASCII只有一种;大多数MBCS(包括GB2312)也只有一种。编码
2.1 维基百科的解释
在计算机科学及相关领域当中,内码指的是“将资讯编码后,透过某种方式储存在特定记忆装置时,装置内部的编码形式”。在不一样的系统中,会有不一样的内码。
在以往的英文系统中,内码为ASCII。在繁体中文系统中,目前经常使用的内码为大五码(Big5)。在简体中文系统中,内码则为国标码(国家标准代码:如今强制要求使用GB18030标准;较旧计算机仍然使用GB2312)。而统一码(Unicode)则为另外一常见内码。
2.2 百度百科的解释
内码是指整机系统中使用的二进制字符编码,是沟通输入、输出与系统平台之间的交换码,经过内码能够达到通用和高效率传输文本的目的。好比MS Word中所存储和调用的就是内码而非图形文字。英文ASCII字符采用一个字节的内码表示,中文字符如国标字符集中,GB23十二、GB1234五、GB13000皆用双字节内码,GB18030(27,533汉字)双字节内码汉字为20,902个,其他6,631个汉字用四字节内码。
下面从计算机对多国语言支持的角度来总结字符编码。
3.1 ASCII编码
如下来自“维基百科”:
ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII则能够勉强显示其余西欧语言。它是现今最通用的单字节编码系统(可是有被UniCode追上的迹象),并等同于国际标准ISO/IEC 646。
ASCII第一次以规范标准的型态发表是在1967年,最后一次更新则是在1986年,至今为止共定义了128个字符;其中33个字符没法显示(这是以现今操做系统为依归,但在DOS模式下可显示出一些诸如笑脸、扑克牌花式等8-bit符号),且这33个字符多数都已经是陈废的控制字符。控制字符的用途主要是用来操控已经处理过的文字。在33个字符以外的是95个可显示的字符,包含用键盘敲下空白键所产生的空白字符也算1个可显示字符(显示为空白)。
ASCII表:见http://zh.wikipedia.org/zh-cn/ASCII
ASCII缺点:
ASCII的最大缺点是只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,所以只能用于显示现代美国英语(并且在处理英语当中的外来词,如naïve、café、élite等等时,全部重音符号都不得不去掉,即便这样作会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题, 但对更多其余语言依然无能为力。所以如今的苹果电脑已经抛弃ASCII而转用Unicode。
最先的英文DOS操做系统的系统内码是:ASCII。计算机这时候只支持英语,其余语言不可以在计算机存储和显示。
在该阶段,单字节字符串使用一个字节存放一个字符(SBCS,Single Byte Character System)。如:"Bob123"占6个字节。
3.2 ANSI编码
为使计算机支持更多语言,一般使用0x800~xFF范围的2个字节来表示1个字符。好比:汉字 '中' 在中文操做系统中,使用 [0xD6,0xD0]这两个字节存储。
不一样的国家和地区制定了不一样的标准,由此产生了GB2312,BIG5,JIS等各自的编码标准。这些使用2个字节来表明一个字符的各类汉字延伸编码方式,称为ANSI编码。在简体中文系统下,ANSI编码表明GB2312编码,在日文操做系统下,ANSI编码表明JIS编码。
不一样ANSI编码之间互不兼容,当信息在国际间交流时,没法将属于两种语言的文字,存储在同一段ANSI编码的文本中。
中文DOS、中文/日文Windows 95/98时代系统内码使用的是ANSI编码(本地化)。
在使用ANSI编码支持多语言阶段,每一个字符使用一个字节或多个字节来表示(MBCS,Multi-Byte Character System),所以,这种方式存放的字符也被称做多字节字符。好比,"中文123" 在中文Windows 95内存中为7个字节,每一个汉字占2个字节,每一个英文和数字字符占1个字节。
在非Unicode环境下,因为不一样国家和地区采用的字符集不一致,极可能出现没法正常显示全部字符的状况。微软公司使用了代码页(Codepage)转换表的技术来过渡性的部分解决这一问题,即经过指定的转换表将非Unicode的字符编码转换为同一字符对应的系统内部使用的Unicode编码。能够在“语言与区域设置”中选择一个代码页做为非Unicode编码所采用的默认编码方式,如936为简体中文GBK,950为正体中文Big5(皆指PC上使用的)。在这种状况下,一些非英语的欧洲语言编写的软件和 文档极可能出现乱码。而将代码页设置为相应语言中文处理又会出现问题,这一状况没法避免。从根本上说,彻底采用统一编码才是解决之道,但目前尚没法作到这 一点。
代码页技术如今普遍为各类平台所采用。UTF-7的代码页是65000,UTF-8的代码页是65001。
3.3 Unicode编码
为了使国际间信息交流更加方便,国际组织制定了UNICODE字符集,为各类语言中的每个字符设定了统一而且惟一的数字编号,以知足跨语言、跨平台进行文本转换、处理的要求。
Unicode字符集能够简写为UCS(Unicode Character Set)。早期的Unicode标准有UCS-二、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。
在UNICODE被采用以后,计算机存放字符串时,改成存放每一个字符在UNICODE字符集中的序号。目前计算机通常使用2个字节(16 位)来存放一个序号(DBCS,Double Byte Character System),所以,这种方式存放的字符也被称做宽字节字符。好比,字符串 "中文123" 在 Windows 2000下,内存中实际存放的是 5 个序号,一共10个字节。
Unicode字符集包含了各类语言中使用到的全部“字符”。用来给 UNICODE 字符集编码的标准有不少种,好比:UTF-8,UTF-7,UTF-16,UnicodeLittle,UnicodeBig等。
4.1 单字节字符编码
(1)编码标准:ISO-8859-1。
(2)说明:最简单的编码规则,每个字节直接做为一个UNICODE字符。好比,[0xD6, 0xD0] 这两个字节,经过iso-8859-1转化为字符串时,将直接获得[0x00D6, 0x00D0]两个UNICODE字符,即 "ÖÐ"。
反之,将UNICODE字符串经过iso-8859-1转化为字节串时,只能正常转化0~255范围的字符。
4.2 ANSI编码
(1)GB2312,BIG5,Shift_JIS,ISO-8859-2。
(2)把UNICODE字符串经过ANSI编码转化为“字节串”时,根据各自编码的规定,一个UNICODE字符可能转化成一个字节或多个字节。
反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。好比,[0xD6, 0xD0]这两个字节,经过GB2312转化为字符串时,将获得[0x4E2D]一个字符,即‘中’字。
“ANSI编码”的特色:
(1)这些“ANSI编码标准”都只能处理各自语言范围以内的UNICODE字符。
(2)“UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。
4.3 UNICODE编码
(1)编码标准:UTF-8,UTF-16,UnicodeBig。
(2)与“ANSI 编码”相似的,把字符串经过UNICODE编码转化成“字节串”时,一个UNICODE字符可能转化成一个字节或多个字节。
与“ANSI 编码”不一样的是:
(1)这些“UNICODE 编码”可以处理全部的UNICODE字符。
(2)“UNICODE 字符”与“转换出来的字节”之间是能够经过计算获得的。
咱们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,咱们只须要知道“编码”的概念就是把“字符”转化成“字节”就能够 了。对于“UNICODE 编码”,因为它们是能够经过计算获得的,所以,在特殊的场合,咱们能够去了解某一种“UNICODE 编码”是怎样的规则。
5.1 GB23十二、GBK和GB18030
(1)GB2312
当中国人们获得计算机时,已经没有能够利用的字节状态来表示汉字,何况有6000多个经常使用汉字须要保存,因而想到把那些ASCII码中127号以后的奇异符号们直接取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一块儿时,就表示一个汉字,前面的一个字节(称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样咱们就能够组合出大约7000多个简体汉字了。在这些编码里,咱们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里原本就有的数字、标点、字母都通通从新编了两个字节长的编码,这就是常说的“全角”字符,而原来在127号如下的那些就叫“半角”字符了。这种汉字方案叫作“GB2312”。GB2312是对ASCII的中文扩展。兼容ASCII。
(2)GBK
可是中国的汉字太多了,咱们很快就就发现有许多人的人名没有办法在这里打出来,不得不继续把GB2312没有用到的码位找出来用上。后来仍是不够用,因而干脆再也不要求低字节必定是127号以后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,无论后面跟的是否是扩展字符集里的内容。结果扩展以后的编码方案被称为“GBK”标准,GBK包括了 GB2312的全部内容,同时又增长了近20000个新的汉字(包括繁体字)和符号。
(3)GB18030
后来少数民族也要用电脑了,因而咱们再扩展,又加了几千个新的少数民族的字,GBK扩成了GB18030。今后以后,中华民族的文化就能够在计算机时代中传承了。
中国的程序员们看到这一系列汉字编码的标准是好的,因而通称他们叫作 "DBCS"(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特色是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,所以他们写的程序为了支持中文处理,必需要注意字串里的每个字节的值,若是这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。在这种状况下,"一个汉字算两个英文字符!"。然而,在Unicode环境下却并不是老是如此。
5.1 Unicode和BigEndianUnicode
这两个指示存储顺序不一样,如"A"的Unicode编码为6500,而BigEndianUnicode编码为0065。
5.2 UTF-七、UTF-8和UTF-16
在Unicode里,全部的字符被一视同仁。汉字再也不使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,如今的汉字是“一个字符”了,因而,拆字、统计字数这些问题也就天然而然地解决了。
可是,这个世界不是理想的,不可能在一晚上之间全部的系统都使用Unicode来处理字符,因此Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。
咱们知道,ASCII字符是单个字节的,好比“A”的ASCII是65。而Unicode是双字节的,好比“A”的Unicode是0065,这就形成了一个很是大的问题:之前处理ASCII的那套机制不能被用来处理Unicode了。
另外一个更加严重的问题是,C语言使用'\0'做为字符串结尾,而Unicode里偏偏有不少字符都有一个字节为0,这样一来,C语言的字符串函数将没法正常处理Unicode,除非把世界上全部用C写的程序以及他们所用的函数库所有换掉。
因而,比Unicode更伟大的东东诞生了,之因此说它更伟大是由于它让Unicode再也不存在于纸上,而是真实的存在于咱们你们的电脑中。那就是:UTF。
UTF= UCS Transformation Format,即UCS转换(传输)格式。
它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。如今流行的UTF有2种:UTF-8和UTF-16。
这两种都是Unicode的编码实现。
5.2.1 UTF-8
UCS-2编码(16进制) UTF-8字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如:“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,因此确定要用三字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,获得:11100110 10110001 10001001,即E6 B1 89。
可见UTF-8是变长的,将Unicode编码为00000000-0000007F的字符,用单个字节来表示;00000080-000007FF的字符用两个字节表示;00000800-0000FFFF的字符用三个字节表示。由于目前为止Unicode-16规范没有指定FFFF以上的字符,因此UTF-8最可能是使用3个字节来表示一个字符。但理论上来讲,UTF-8最多须要用6字节表示一个字符。
UTF-8兼容ASCII。
5.2.2 UTF-16(标准的Unicode成为UTF-16)
UTF-16和上面提到的Unicode自己的编码规范是一致的。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对 于不小于0x10000的UCS码,定义了一个算法。不过因为实际使用的UCS2,或者UCS4的BMP必然小于0x10000,因此就目前而言,能够认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,因此就不得不考虑字节序的问题。
UTF-16不兼容ASCII。
5.2.3 UTF-7
UTF-7 (7-位元 Unicode 转换格式(Unicode Transformation Format,简写成 UTF)) 是一种可变长度字元编码方式,用以将Unicode字元以ASCII编码的字元串来呈现,能够应用在电子邮件传输之类的应用。
UTF-7并不是Unicode标准之一。想要详细了解的能够查阅相关资料。
Unicode是内存编码表示方案(是规范),而UTF是如何保存和传输Unicode的方案(是实现)。
6.1 UTF的字节序和BOM
6.1.1 字节序
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每一个编码单元 的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。若是咱们收到UTF-16字节流“594E”,那么这是“奎”仍是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。
BOM是一个有点小聪明的想法:
在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。
UTF-8不须要BOM来代表字节顺序,但能够用BOM来代表编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者能够用咱们前面介绍的编码方法验证一下)。因此若是接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
6.1.2 BOM
(1)BOM的来历
为了识别Unicode文件,Microsoft建议全部的Unicode文件应该以ZERO WIDTH NOBREAK SPACE(U+FEFF)字符开头。这做为一个“特征符”或“字节顺序标记(byte-order mark,BOM)”来识别文件中使用的编码和字节顺序。
(2)不一样的系统对BOM的支持
由于一些系统或程序不支持BOM,所以带有BOM的Unicode文件有时会带来一些问题。
①JDK1.5以及以前的Reader都不能处理带有BOM的UTF-8编码的文件,解析这种格式的xml文件时,会抛出异常:Content is not allowed in prolog。“对于解决方法,以后我会写篇文章专门讨论该问题。”
②Linux/UNIX 并无使用BOM,由于它会破坏现有的ASCII文件的语法约定。
③不一样的编辑工具对BOM的处理也各不相同。使用Windows自带的记事本将文件保存为UTF-8编码的时候,记事本会自动在文件开头插入BOM(虽然BOM对UTF-8来讲并非必须的)。而其它不少编辑器用不用BOM是能够选择的。UTF-八、UTF-16都是如此。
(3)BOM与XML
XML解析读取XML文档时,W3C定义了3条规则:
①若是文档中有BOM,就定义了文件编码;
②若是文档中没有BOM,就查看XML声明中的编码属性;
③若是上述二者都没有,就假定XML文档采用UTF-8编码。
6.2 决定文本的字符集与编码
软件一般有三种途径来决定文本的字符集和编码。
(1)对于Unicode文本最标准的途径是检测文本最开头的几个字节。如:
开头字节 Charset/encoding
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian(UTF-16LE)
FF FE UTF-16/UCS-2, big endian(UTF-16BE)
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endia
(2)采起一种比较安全的方式来决定字符集及其编码,那就是弹出一个对话框来请示用户。
然而MBCS文本(ANSI)没有这些位于开头的字符集标记,如今不少软件保存文本为Unicode时,能够选择是否保存这些位于开头的字符集标记。所以,软件不该该依赖于这种途径。这时,软件能够采起一种比较安全的方式来决定字符集及其编码,那就是弹出一个对话框来请示用户。
(3)采起本身“猜”的方法。
若是软件不想麻烦用户,或者它不方便向用户请示,那它只能采起本身“猜”的方法,软件能够根据整个文本的特征来猜想它可能属于哪一个 charset,这就极可能不许了。使用记事本打开那个“联通”文件就属于这种状况。(把本来属于ANSI编码的文件当成UTF-8处理,详细说明 见:http://blog.csdn.net/omohe/archive/2007/05/29/1630186.aspx)
6.3 记事本的几种编码
(1)ANSI编码
记事本默认保存的编码格式是:ANSI,即本地操做系统默认的内码,简体中文通常为GB2312。这个怎么验证呢?用记事本保存后,使用 EmEditor、EditPlus和UltraEdit之类的文本编辑器打开。推荐使用EmEditor,打开后,在又下角会显示编码:GB2312。
(2)Unicode编码
用记事本另存为时,编码选择“Unicode”,用EmEditor打开该文件,发现编码格式是:UTF-16LE+BOM(有签名)。用十六进制方式查看,发现开头两字节为:FF FE。这就是BOM。
(3)Unicode big endian
用记事本另存为时,编码选择“Unicode”,用EmEditor打开该文件,发现编码格式是:UTF-16BE+BOM(有签名)。用十六进制方式查看,发现开头两字节为:FE FF。这就是BOM。
(4)UTF-8
用记事本另存为时,编码选择“UTF-8”,用EmEditor打开该文件,发现编码格式是:UTF-8(有签名)。用十六进制方式查看,发现开头三个字节为:EF BB BF。这就是BOM。
7.1 误解一
在将“字节串”转化成“UNICODE 字符串”时,好比在读取文本文件时,或者经过网络传输文本时,容易将“字节串”简单地做为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。
而实际上,在非英文的环境中,应该将“字节串”做为 ANSI 字符串,采用适当的编码来获得 UNICODE 字符串,有可能“多个字节”才能获得“一个字符”。
一般,一直在英文环境下作开发的程序员们,容易有这种误解。
7.2 误解二
在DOS,Windows 98等非UNICODE环境下,字符串都是以ANSI编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪一种编码才能被正确地使用。这使咱们造成了一个惯性思惟:“字符串的编码”。
当UNICODE被支持后,Java中的String是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,所以已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个ANSI字符串时,才有编码的概念。
很多的人都有这个误解。
7.3 分析与解决
第一种误解,每每是致使乱码产生的缘由。第二种误解,每每致使原本容易纠正的乱码问题变得更复杂。
在这里,咱们能够看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用iso-8859-1进行转化。所以,咱们经常使用bytes = string.getBytes("iso-8859-1") 来进行逆向操做,获得原始的“字节串”。而后再使用正确的ANSI编码,好比string = new String(bytes, "GB2312"),来获得正确的“UNICODE 字符串”。
8.1 《字符,字节和编码》http://www.regexlab.com/zh/encoding.htm(强烈推介)
8.4 百度百科《Unicode》http://baike.baidu.com/view/40801.htm
8.5 《Unicode与UTF-8/UTF-16之间有啥联系或区别?》http://zhidao.baidu.com/question/52532619.html?fr=ala0