字符编码问题一直深深困扰着我~无论是网页还是数据库抑或是单纯的文件字符流,总有各种奇怪的编码问题。之所以称之为奇怪其实主要还是因为我对于编码的知识了解太浅。近来深刻觉醒编码问题非解决不行,故将所阅读的资料内容概括整理如下。
GB2312字符集:只收录简化字汉字,以及一般常用字母和符号,主要通行于中国大陆地区和新加坡等地。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。GB2312采用2个字节,共收录有7445个字符,其中简化汉字6763个,字母和符号682个。GB2312 将所收录的字符分为94个区,编号为01区至94区;每个区收录94个字符,编号为01位至94位。GB2312的每一个字符都由与其唯一对应的区号和位号所确定。例如:汉字“啊”,编号为16区01位。GB2312 字符集的区位分布表:
参考资料(1)维基百科-字符编码(2)《计算机编码知识——区位码、国标码、机内码、输入码、字形码》(3)《计算机内码与外码的区别》(4)《和荣笔记- GB2312 字符集与编码对照表》(5)《说说字符集和编码》(6)《编码简介》(7)《深入了解字符集和编码》(8)吴秦 《字符集和字符编码(Charset & Encoding)》
计算机在存储和传输文件信息(图片,文档,音频,视频等)的过程中,都是使用二进制的比特流形式。
所以要保持文件信息,就需要把文件信息按一定的规则转成二进制的比特流进行存储。
要查看文件信息,计算机也需要把拿到的文件信息(二进制比特流)按一定的规则转成我们能识别的东西。
这里所说的规则,即指编码和解码方式。
当把原始文件转成二进制比特流的编码方式和把二进制比特流转成文件的解码方式不兼容的情况下,就会出现乱码。
下面介绍字符集,字符编码。
字符集:是多个字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
字符集对应到生活中就是对某种语言的称呼,如英语、日语、汉语等。
字符集规定了某个字符对应的二进制数值存放方式(编码)和某串二进制数值代表了哪个字符(解码)的转换关系。
那么为什么有很多种不相同的字符集呢?
就像世界上有不同的语言,不同地区或组织在最初制定字符集的时候,并不会意识到这将会是以后全球普适的准则,或者对于自身利益的考虑,就产生了那么多具有相同效果但又不相互兼容的标准了。
字符编码:是一套法则,使用该法则能够将字符集合中的每个字符与计算机能识别的一个二进制数值进行一一配对。
对于一个字符集来说,要正确编码解码一个字符需要三个关键元素:字库表、字符集、字符编码。
字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
字符集,即用一个编码值code point来表示一个字符在字库中的位置。
每个字符在字库表中都有唯一的序号,为什么不把该唯一序号作为存储内容,却还要把该字符通过字符编码转成另外一种格式呢?
这是因为统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。
我们平常接触到的字符集有下面几种:
计算机刚出现的时候,主要在西方国家应用,当时的字符集,ASCII 字符集主要用于显示现代英语和其他西欧语言。
对应于ASCII编码,用一个字节表示一个字符。
中国国家标准总局发布了主要用于表示简体中文的字符集,GB2312字符集。
对应于GB2312编码,用两个字节表示一个字符。
因为GB2312并不能处理一些罕用字等,又出现了GBK字符集。GBK是从GB2312扩展而来,兼容GB2312。
对应于GBK编码,用两个字节表示一个字符。
台湾的多个组织共同发布了主要用于表示繁体中文的字符集,BIG5字符集。
对应于BIG编码,用两个字节表示一个字符。
Unicode 学术学会机构制订的Unicode字符集,收录了多种语言的字符并设定了统一且唯一的二进制码,支持现今世界各种不同语言的书面文本的交换、处理及显示。
Unicode字符集有多种字符编码方式:UTF-32编码 / UTF-16编码 / UTF-8编码。
UTF-32:每个字符都使用四个字节表示一个字符。
UTF-16:将0–65535范围内的字符都使用两个字节表示一个字符,65535范围外的字符用其他技巧
UTF-8:是一种可变长度的字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且与ASCII兼容,用一至四个字节为每个字符进行编码。(中文一般用三个字节进行编码)
我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数字.你肯定不能想怎么转换就怎么转,必须得有定些规则.于是刚开始的时候就有ASCII字符集(American Standard Code for Information Interchange, "美国信息交换标准码),它使用7 bits来表示一个字符,总共表示128个字符,我们一般都是用字节(byte,即8个01串)来作为基本单位.那么怎么当用一个字节来表示字符时第一个bit总是0,剩下的七个字节就来表示实际内容.后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符.也就是当第一个bit是0时仍表示之前那些常用的字符.当为1时就表示其他补充的字符.
英文字母再加一些其他标点字符之类的也不会超过256个.一个字节表示主足够了.但其他一些文字不止这么多 ,像汉字就上万个.于是又出现了其他各种字符集.这样不同的字符集交换数据时就有问题了.可能你用某个数字表示字符A,但另外的字符集又是用另外一个数字表示A.这样交互起来就麻烦了.于是就出现了Unicode和ISO这样的组织来统一制定一个标准,任何一个字符只对应一个确定的数字.ISO取的名字叫UCS(Universal Character Set),Unicode取的名字就叫unicode了.
总结起来为啥需要Unicodey就是为了适应全球化的发展,便于不同语言之间的兼容交互,而ASCII不再能胜任此任务了.
1.容易产生后歧义的两字节
unicode的第一个版本是用两个字节(16bit)来表示所有字符
.实际上这么说容易让人产生歧义,我们总觉得两个字节就代表保存在计算机中时是两个字节.于是任何字符如果用unicode表示的话保存下来都占两个字节.其实这种说法是错误的.
其实Unicode涉及到两个步骤,首先是定义一个规范,给所有的字符指定一个唯一对应的数字,这完全是数学问题,可以跟计算机没半毛钱关系.第二步才是怎么把字符对应的数字保存在计算机中,这才涉及到实际在计算机中占多少字节空间.
所以我们也可以这样理解,Unicode是用0至65535之间的数字来表示所有字符.其中0至127这128个数字表示的字符仍然跟ASCII完全一样.65536是2的16次方.这是第一步.第二步就是怎么把0至65535这些数字转化成01串保存到计算机中.这肯定就有不同的保存方式了.于是出现了UTF(unicode transformation format),有UTF-8,UTF-16.
UTF-16比较好理解,就是任何字符对应的数字都用两个字节来保存.我们通常对Unicode的误解就是把Unicode与UTF-16等同了.但是很显然如果都是英文字母这做有点浪费.明明用一个字节能表示一个字符为啥整两个啊.
于是又有个UTF-8,这里的8非常容易误导人,8不是指一个字节,难道一个字节表示一个字符?实际上不是.当用UTF-8时表示一个字符是可变的,有可能是用一个字节表示一个字符,也可能是两个,三个..反正是根据字符对应的数字大小来确定.
于是UTF-8和UTF-16的优劣很容易就看出来了.如果全部英文或英文与其他文字混合,但英文占绝大部分,用UTF-8就比UTF-16节省了很多空间.而如果全部是中文这样类似的字符或者混合字符中中文占绝大多数.UTF-16就占优势了,可以节省很多空间.另外还有个容错问题,等会再讲
看的有点晕了吧,举个例子.假如中文字"汉"对应的unicode是6C49(这是用十六进制表示,用十进制表示是27721为啥不用十进制表示呢?很明显用十六进制表示要短点.其实都是等价的没啥不一样.就跟你说60分钟和1小时一样.).你可能会问当用程序打开一个文件时我们怎么知道那是用的UTF-8还是UTF-16啊.自然会有点啥标志,在文件的开头几个字节就是标志.
EF BB BF 表示UTF-8
FE FF 或者 FF FE 表示UTF-16.
用UTF-16表示"汉"
假如用UTF-16表示的话就是01101100 01001001(共16 bit,两个字节).程序解析的时候知道是UTF-16就把两个字节当成一个单元来解析.这个很简单.
用UTF-8表示"汉"
用UTF-8就有复杂点.因为此时程序是把一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把1个还是两个或三个字节做为一个单元来处理.
0xxxxxxx,如果是这样的01串,也就是以0开头后面是啥就不用管了XX代表任意bit.就表示把一个字节做为一个单元.就跟ASCII完全一样.
110xxxxx 10xxxxxx.如果是这样的格式,则把两个字节当一个单元
1110xxxx 10xxxxxx 10xxxxxx 如果是这种格式则是三个字节当一个单元.
这是约定的规则.你用UTF-8来表示时必须遵守这样的规则.我们知道UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符.
而UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符.而三个字节能表示2的16次方,65536个字符.
由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示.
所有要用1110xxxx 10xxxxxx 10xxxxxx这种格式.把27721对应的二进制从左到右填充XXX符号(实际上不一定从左到右,也可以从右到左,这是涉及到另外一个问题.等会说.
刚说到填充方式可以不一样,于是就出现了Big-Endian,Little-Endian的术语.Big-Endian就是从左到右,Little-Endian是从右到左.
由上面我们可以看出UTF-8在局部的字节错误(丢失、增加、改变)不会导致连锁性的错误,因为 UTF-8 的字符边界很容易检测出来,所以容错性较高。
Unicode版本2
前面说的都是unicode的第一个版本.但65536显然不算太多的数字,用它来表示常用的字符是没一点问题.足够了,但如果加上很多特殊的就也不够了.于是从1996年开始又来了第二个版本.用四个字节表示所有字符.这样就出现了UTF-8,UTF16,UTF-32.原理和之前肯定是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示.然后UTF-8,UTF-16就视情况而定了.UTF-8可以选择1至8个字节中的任一个来表示.而UTF-16只能是选两字节或四字节..由于unicode版本2的原理完全是一样的,就不多说了.
前面说了要知道具体是哪种编码方式,需要判断文本开头的标志,下面是所有编码对应的开头标志
EF BB BF UTF-8FE FF UTF-16/UCS-2, big endianFF FE UTF-16/UCS-2, little endianFF FE 00 00 UTF-32/UCS-4, little endian.00 00 FE FF UTF-32/UCS-4, big-endian.
程序开发常见的ASCII、GB2312、GBK、GB18030、UTF8、ANSI、Latin1中文编码到底有何不同?如果你在业务中也曾经被乱码搞晕过,不妨一起探究一下。
在计算机眼里读到的所有文字都是由0和1组成的字符串,为了能让汉字正常显示在屏幕上,我们需要做以下两件事情:
这里需要说明的是,第2件事情并不是直接把数字编号用二进制表示出来那么简单,还要处理多个字连在一起的时候如何做分隔的问题。
例如如果我把”腾”编为1号(二进制00000001,占1byte),把“讯”编为5号(二进制00000101,占1byte),汉字这么多,一定还有一个汉字被编为了133号(二进制00000001 00000101,占2bytes)。
那么现在问题来了,当计算机读到00000001 00000101这一串的时候,它应该显示“腾讯”两个字还是显示那一个133号的文字?因此如何做分隔也是字符编码需要考虑的事情。
第2件事情通常解决方案要么就是规定好每个字长度(例如所有文字都是2bytes,不够的前面用0补齐),要么就是在用0和1表示的时候,不仅需要表示出数字编码,还要暗示给计算机接下来多少个连续byte构成一个字,这个后面UTF8编码中会提到。
几种常见中文编码之间存在兼容性,一图胜千言
所谓兼容性可以简单理解为子集,同时存在也不冲突,不会出现上文所说的不知道是“腾讯”还是133号文字的情况。
图中我们可以看出,ASCII被所有编码兼容,而最常见的UTF8与GBK之间除了ASCII部分之外没有交集,这也是平时业务中最常见的导致乱码场景,使用UTF8去读取GBK编码的文字,可能会看到各种乱码。而GB系列的几种编码,GB18030兼容GBK,GBK又兼容GB2312,下文细讲。
ASCII编码每个字母或符号占1byte(8bits),并且8bits的最高位是0,因此ASCII能编码的字母和符号只有128个。有一些编码把8bits最高位为1的后128个值也编码上,使得1byte可以表示256个值,但是这属于扩展的ASCII,并非标准ASCII。通常所说的标准ASCII只有前128个值!
ASCII编码几乎被世界上所有编码所兼容(UTF16和UTF32是个例外),因此如果一个文本文档里面的内容全都由ASCII里面的字母或符号构成,那么不管你如何展示该文档的内容,都不可能出现乱码的情况。
GB全称GuoBiao国标,GBK全称GuoBiaoKuozhan国标扩展。GB18030编码兼容GBK,GBK兼容GB2312,其实这三种编码有着非常深厚的渊源,我们放在一起进行比较。
【GB2312】
GB2312编码表有个值得注意的点,这个表中也有一些数字和字母,与ASCII里面的字母非常像。例如A3B2对应的是数字2(如下图),但是ASCII里面50(十进制)对应的也是数字2。他们的区别就是输入法中所说的“半角”和“全角”。全角的数字2占两个字节。
通常,我们在打字或编程中都使用半角,即ASCII来编写数字或英文字母。特别是编程中,如果写全角的数字或字母,编译器很有可能不认识……
【GBK】
【GB18030】
然而,GBK的两万多字也已经无法满足我们的需求了,还有更多可能你自己从来没见过的汉字需要编码。
这时候显然只用2bytes表示一个字已经不够用了(2bytes最多只有65536种组合,然而为了和ASCII兼容,最高位不能为0就已经直接淘汰了一半的组合,只剩下3万多种组合无法满足全部汉字要求)。因此GB18030多出来的汉字使用4bytes编码。当然,为了兼容GBK,这个四字节的前两位显然不能与GBK冲突(实操中发现后两位也并没有和GBK冲突)。
GB2312,GBK,GB18030都是采取了固定长度的办法来解决字符分隔(即前文所提的第2件事情)问题。GBK和GB2312比ASCII多出来的字都是2bytes,GB18030比GBK多出来的字都是4bytes。至于他们具体是如何做到兼容的,可以参考下图:
这图中展示了前文所述的几种编码在编码完成后,前2个byte的值域(用16进制表示)。每个byte可以表示00到FF(即0至255)。ASCII编码由于是单字节,所以没有第2位。因为GBK兼容GB2312,所以理论上上图中GB2312的领土面积也可以算在GBK的范围内,GB18030也同理。
上图只是展示出了比之前编码“多”出来的面积。GB18030由于是4bytes编码,上图只是展示了前2bytes的值域,虽然面积最小,但是如果后2bytes也算上,GB18030新编码的字数实际上远远多于GBK。
可以看出为了做到兼容性,以上所有编码的前2bytes做到了相互值域不冲突,这样就可以允许几种不同编码中的文字同时出现在同一个文本文件中。只要全都按照GB18030编码的规则去解析并展示文件,就不会有乱码出现。实际业务中GB18030很少提到,通常GBK见得比较多,这是因为如果你去看一下GB18030里面所编码的文字,你会发现自己一个字也不认识……
99%的前端写网页时都会加上<meta charset="utf-8">,99%的后端工程师新建数据库表时都会加上DEFAULT CHARSET=utf8(剩下的1%应该是忘了写)。
之所以我们想让UTF8一统天下,就是因为UTF8可以表示出世界上所有的文字!UTF8与前面说的GB系列编码不兼容,所以如果一个文件中即有UTF8编码的文字,又有GB18030编码的文字,那绝对会有乱码。
Unicode赋予了全世界所有文字和符号一个独一无二的数字编号,UTF8所做的事情就是把这个数字编号表示出来(即解决前文提到的第2件事情)。UTF8解决字符间分隔的方式是数二进制中最高位连续1的个数来决定这个字是几字节编码。0开头的属于单字节,和ASCII码重合,做到了兼容。
以三字节为例,开头第一个字节的”1110”,有连续三个1,说明包括本字节在内,接下来三个字节一起构成了一个文字。凡是不属于文字首字节的byte都以“10”开头,上表中标注X的位置才是真正用来表示Unicode数值的。
这种巧妙设计,把Unicode的数值和每个字的字节数融合在一起,最坏情况是6个字节表示一个字,已经足够表示世界上所有语言的所有文字了。不过从这种表示方式也可以很显然地看出来,UTF8和GBK没有任何关系,除了都兼容ASCII以外。
举例说明,中文“鹅”字,Unicode十进制值为40517(16进制为9E45,2进制为1001 1110 0100 0101)。这个2进制值长度为12位,查询上面表格发现,二字节不够表示,四字节太长,三字节刚好,因此可以表示为 11101001 10111001 10000101,换算为16进制即E9B985,这就是“鹅”字的UTF8编码,占3字节。另外,经查询,“鹅”的GBK编码为B6EC,和UTF8的值完全不相干。
对于中文汉字来说,所有常用汉字的Unicode值都可以用3字节的UTF8表示出来,而GBK编码的汉字基本是2字节(GB18030虽4字节但是日常没人会写那些字)。这也就导致了,如果把GBK编码的中文文本另存为UTF8编码,体积会大50%左右。这也是UTF8的一点小瑕疵,存储同样的汉字,体积比GBK要大50%。
不过在“可表示世界上所有文字”这一巨大优势面前,UTF8的这点小瑕疵可以忽略了,所以日常开发中最常使用UTF8。
【ANSI编码】
准确说,并不存在哪种具体的编码方式叫做ANSI,它只是一个Windows操作系统上的别称而已。在中文简体Windows操作系统上,ANSI就是GBK;在泰语操作系统上,ANSI就是TIS-620(一种泰语编码);在韩语操作系统上,ANSI就是EUC-KR(一种韩语编码)。并且所谓的ANSI只存在于Windows操作系统上。
【Latin1编码(又名ISO-8859-1编码)】
相信99%的人第一次听到Latin1都是在使用Mysql数据库的时候接触到的。Latin1是Mysql数据库表的默认编码方式。Latin1也是单字节编码方式,也就是说最多只能表示256个字母或符号,并且前128个和ASCII完全吻合。
Latin1在ASCII基础上又充分利用了后面那128个值,赋予他们一些泰语、希腊语等字母或符号,将1个字节的256个值全部占满了。因为项目中用不到,我对这种编码的细节没兴趣了解,唯一感兴趣的是为什么Mysql选它做默认编码(为什么默认编码不是UTF8)?以及如果忘了设置Mysql表的编码方式时,用Latin1存储中文会不会出问题?
为什么默认编码是Latin1而不是UTF8?原因之一是Mysql最开始是某瑞典公司搞的项目,故默认collate都是latin1_swedish_ci。swedish可以理解为其私心,不过latin1不管是否出于私心目的,单字节编码作为默认值肯定是比多字节做默认值更不容易在插入数据时报错。
既然Latin1为单字节编码,并且将1个字节的所有256个值全部占满,因此理论上把任何编码的值塞到Latin1字段都是可以存的(无非就是显示乱码而已)。
假设默认为UTF8这一多字节编码,在用户误把一个不使用UTF8编码的字符串存进去时,很有可能因为该字符串不符合UTF8的编码要求导致Mysql根本没法处理。这也是单字节编码的一大好处:显示可以乱码,但是里面的数据值永远正确。
用Latin1存储中文有没有问题?答案是没有问题,但是并不建议。例如你把UTF8编码的“讯”字(UTF8编码为0xE8AEAF,占三个字节)存入了Latin1编码的Mysql表,那么在Mysql眼里,你存入的并不是一个“讯”字,而是三个Latin1的字母(0xE8,0xAE,0xAF)。本质上,你存的数据值依然是0xE8AEAF,这种“欺骗”Mysql的行为并没有导致数据丢失,只不过你需要注意读取出来该值的时候,自己要以UTF8编码的方式显示出来,要不然就是乱码。
文章最后,遗留一个问题。既然有这么多编码形式,如果给定一个文本文件,不告诉你是什么编码,如何用程序进行检测?比较有代表性的编码检测库为python的chardet,其具体的原理,且待下回分解~
本文介绍了三种自动检测方法来确定没有显式字符集声明的文档编码。我们讨论了每种方法的优缺点,并提出了一种组合方法,其中将所有这三种类型的检测方法都用于发挥其最大优势并补充其他检测方法。我们认为,自动检测在帮助浏览器用户从频繁使用字符编码菜单转变为更不希望使用编码菜单的更为理想的状态方面起着重要作用。我们设想向Unicode的过渡。用户必须知道如何正确显示字符-无论是本机编码还是Unicode编码之一。
让我们从一个通用模式开始。对于大多数应用程序,以下内容代表自动检测使用的一般框架:输入数据->``自动检测器->返回结果''应用程序/程序从自动检测器获取返回的结果,然后将此信息用于有多种用途,例如设置数据的编码,按原始创建者的意图显示数据,将其传递给其他程序等等。本文讨论的自动检测方法以Internet浏览器应用程序为例。但是,这些自动检测方法可以轻松适用于其他类型的应用程序。
浏览器可能会使用某些检测算法来自动检测网页的编码。程序可以采用不同的编码方式以多种方式解释一段文本,但是在某些极少数情况下,页面作者只需要一种解释即可。通常,这是用户唯一合理的方式以预期的语言正确看到该页面。为了列出设计自动检测算法的主要因素,我们从有关输入文本及其处理方法的某些假设开始。以网页数据为例,1.输入文本由特定读者可读的单词/句子组成。语言。(=数据不乱码。)2.输入的文本来自Internet上的典型网页。(=数据通常不是来自某些死语或古老的语言。)3.输入文本可能包含与编码无关的外来噪音,例如HTML标签,非本地单词(例如中文文档中的英语单词),空格和其他格式/控制字符。覆盖所有已知的语言和编码以进行自动检测几乎是不可能完成的任务。在当前的方法中,我们试图涵盖东亚语言中使用的所有流行编码,并提供了一个通用模型来同时处理单字节编码。选择了俄语编码作为后一种类型的实现示例,并且选择了我们的单字节编码测试平台。4.目标多字节编码包括UTF8,Shift-JIS,EUC-JP,GB2312,Big5,EUC-TW,EUC-KR,ISO2022-XX和HZ。5.提供一个处理单字节编码的通用模型。俄罗斯语言编码(KOI8-R,ISO8859-5,window1251,Mac-cyrillic,ibm866,ibm855)在测试台中作为实现示例进行了介绍。
在本节中,我们讨论3种不同的方法来检测文本数据的编码。它们是1)编码方案方法,2)字符分布和3)2字符序列分布。每个人都有其优点和缺点,但是,如果我们以互补的方式使用所有三个,则结果可能会非常令人满意。
对于多字节编码,此方法可能是最明显的方法,也是最常尝试的方法。在任何多字节编码编码方案中,并未使用所有可能的代码点。如果在验证某种编码时遇到非法字节或字节序列(即未使用的代码点),我们可以立即得出结论,这不是正确的猜测。少数代码点还特定于某种编码,该事实可能会导致立即得出肯定的结论。Netscape Communications的Frank Tang开发了一种非常有效的算法,可以通过并行状态机使用编码方案来检测字符集。他的基本思想是:对于每种编码方案,实现一个状态机以验证此特定编码的字节序列。检测器收到的每个字节,都会将该字节提供给每个可用的活动状态机,一次提供一个字节。状态机根据其先前的状态及其接收的字节来更改其状态。状态机中有3个自动检测器感兴趣的状态:
在典型示例中,一个状态机最终将提供肯定的答案,而其他所有状态机将提供否定的答案。当前工作中使用的PSM(并行状态机)版本是Frank Tang原始工作的修改。每当状态机达到START状态(表示它已成功识别合法字符)时,我们都会查询状态机以查看此字符有多少字节。该信息有两种使用方式。
在任何给定的语言中,某些字符比其他字符更常用。此事实可用于为每种语言脚本设计数据模型。这对于具有大量字符的语言(例如中文,日语和韩语)特别有用。我们经常听到有关这种分布统计的轶事,但是我们没有发现很多公开的结果。因此,在以下讨论中,我们主要依靠自己收集的数据。
我们对GB2312中编码的6763个汉字数据进行了研究,得出以下分布结果:
表1.简体中文字符分配表
台湾普通话促进委员会每年进行的研究表明,使用Big5编码的繁体中文具有类似的结果。
最常见字符数
累计百分比
0.11713
0.29612
0.42261
0.57851
0.74851
0.89384
0.97583
0.99910
表2.繁体字分布表
我们收集了自己的日语数据,然后编写了实用程序对其进行分析。
表3. 日语字符分布表
同样,对于韩语用户,我们从Internet收集了自己的数据并在其上运行实用程序。结果如下:
表4 ..韩文字符分布表
在所有这四种语言中,我们发现很少的编码点集覆盖了我们定义的应用范围中使用的很大比例的字符。此外,仔细检查那些常用的编码点会发现它们分散在相当宽的编码范围内这为我们提供了一种克服编码方案分析器中遇到的常见问题的方法,即不同的国家编码可能共享重叠的代码点。由于这些语言最频繁出现的集合具有上述特征,因此之间存在重叠问题在编码方案方法中,不同的编码在分发方法中将无关紧要。
为了基于字符频率/分布统计信息识别语言的特征,我们需要一种算法来从文本输入流中计算值。此值应显示此文本流采用某种字符编码的可能性。一个自然的选择可能是根据每个字符的频率权重来计算该值。但是从我们使用各种字符编码进行的实验中,我们发现这种方法不是必需的,它使用了过多的内存和CPU能力。简化版本提供了非常令人满意的结果,并且使用更少的资源并且运行更快。在当前方法中,给定编码中的所有字符都分为两类,``经常使用''和``不经常使用''。如果一个字符在频率分布表的前512个字符中,则归为经常使用的字符。选择数字512是因为它覆盖了4种语言输入文本中任何一个的大量累积百分比,而只占用了一小部分编码点。我们计算一批输入文本中任一类别中的字符数,然后计算一个浮点值,我们称为“分配比率”。分配比率定义如下:分配比率= 512个最常用字符的出现次数除以其余字符的出现次数。实际上,所测试的每种多字节编码都显示出不同的分配比率。然后,可以从该比率计算给定编码的原始输入文本的置信度。下面对每种编码的讨论应该使这一点更加清楚。
让我们看一下4种语言数据以了解分配比率的差异。首先请注意,我们以两种方式使用术语分配比率。为语言脚本/字符集而不是编码定义了一个``理想的分配比率''。如果语言脚本/字符集由不止一种编码表示,则对于每种编码,我们将计算``实际的分配比率''通过将字符分类为``常用''或``不常用''类别来输入数据。然后将该值与语言脚本/字符集的理想分配比率进行比较。基于获得的实际分配比率,我们可以如下所述为每组输入数据计算置信度。
GB2312编码包含两个级别的汉字。级别1包含3755个字符,级别2包含3008个字符。级别1的字符比级别2的字符更频繁使用,并且不足为奇的是,GB 2312的最常用字符列表上的所有512个字符均在级别1内。字符均匀地分散在3755个代码点中。在第1级中,这些字符占所有编码点的13.64%,但在典型的中文文本中,这些字符占79.135%。在理想情况下,一段包含足够字符的中文文本应该返回以下内容:``分配比率= 0.79135 /(1-0.79135)= 3.79对于使用相同编码方案的随机生成的文本,如果不使用2级字符,则比率应约为512 /(3755-512)= 0.157。如果考虑到第2级字符,我们可以假设每个第1级字符的平均概率为p1,而第2级字符的平均概率为p2。然后计算为:512 * p1 /(3755 * p1 + 3008 * p2 512 * p1)= 512 /(3755 + 3008 * p2 / p1-512)显然,该值甚至更小。在以后的分析中,我们仅使用最坏的情况进行比较。
Big5和EUC-TW(即CNS字符集)编码的故事非常相似.Big5还以2级编码中文字符。最常用的512个字符均匀地分散在5401 1级字符中。我们从big5编码的文本中可以获得的理想比率是:分配比率= 0.74851 /(1-0.74851)= 2.98并且对于随机生成的文本,其比率应接近512 /(5401-512)= 0.105,因为Big5级别1字符几乎与CNS平面1字符相同,相同的分析适用于EUC-TW。
对于日语,平假名和片假名通常比汉字使用更多。由于Shift-JIS和EUC-JP在不同的编码范围内对平假名和片假名进行编码,因此我们仍然可以使用此方法来区分这两种编码。那些最经常使用的512个汉字字符也均匀地分散在2965 JIS Level 1汉字集之间。相同的分析得出以下分配比率:分配比率= 0.92635 /(1-0.92635)= 12.58对于随机生成日语文本数据,比率至少应为512 /(2965 + 63 + 83 + 86-512)= 0.191。计算包括片假名(63),平假名(83)和片假名(86)。
在EUC-KR编码中,典型韩文文本中实际使用的Hanja(中文)字符的数量微不足道。以这种编码方式编码的2350个韩文字符按其发音排列。在频率表中,我们通过分析大量的韩文文本数据获得了最常用的字符均匀分布在这2350个代码点中的信息。使用相同的分析,在理想情况下,我们得到:分配比率= 0.98653 /(1-0.98653)= 73.24对于随机生成的韩文,应为:512 /(2350-512)= 0.279。
通过前面对每种语言脚本的讨论,我们可以为每个数据集定义置信度级别,如下所示:置信度检测(InputText){``InputText中的每个多字节字符。{TotalCharacterCount++;如果该字符在512个最频繁的字符中,则FrequentCharacterCount ++;}比率= FrequentCharacterCount/(TotalCharacterCount-FreqentCharacterCount);置信度=比率/ CHARSET_RATIO;回报信心;}给定集合数据的置信度定义为输入数据的分配比率除以通过前面各节中的分析获得的理想分配比率。
在仅使用少量字符的语言中,我们需要进一步计算每个单个字符的出现次数。字符组合揭示了更多的语言特性信息。我们将2字符序列定义为2个字符,它们在输入文本中紧挨着出现,在这种情况下顺序很重要。正如并非所有字符在一种语言中使用频率一样高一样,2-字符序列分布也被证明与语言/编码密切相关。此特征可用于语言检测。这样可以提高检测字符编码的信心,并且在检测单字节语言时非常有用。让我们以俄语为例。我们下载了大约20MB的俄语纯文本,并编写了一个分析文本的程序。该程序共发现21,199,528个2字符序列。在我们发现的序列中,其中一些与我们的考虑无关,例如空间-空间组合。这些序列被视为噪声,并且它们的出现不包括在分析中。在我们用来检测俄语编码的数据中,剩下的20,134,122个2字符序列出现了。这覆盖了数据中发现的所有序列出现的约95%。构建我们的语言模型所使用的序列可以是分为4096个不同的序列,在我们的20,134,122个样本中,其中1961年出现的次数少于3次。我们将这1961个序列称为该语言的负序列集。
对于单字节语言,我们将置信度级别定义如下:置信度检测(InputText){为InputText中的每个字符。{。如果字符不是符号或标点符号TotalCharacters ++;在频率表中找到其频率顺序;If(频率顺序<SampleSize){{FrequentCharCount ++;如果没有lastChar {lastChar = thisChar;继续}如果lastChar和thisChar都是我们的样本范围内{TotalSequence ++;如果Sequence(lastChar,thisChar)属于NegativeSequenceSet NetgativeSequenceCount++;}}}置信度=(TotalSequence NegativeSequenceCount)/ TotalSequence* FrequentCharCount / TotalCharacters;返回信心;} 里有算法需要说明的几件事情。首先,未对所有字符进行此序列分析。我们可以构建一个256 x 256的矩阵来覆盖所有这些序列,但是其中许多序列与语言/编码分析无关,因此是不必要的。由于大多数单字节语言使用的字母少于64个,因此似乎最常用的64个字符几乎涵盖了所有特定于语言的字符。通过这种方式,矩阵可以减少为64乘以64,这要小得多。因此我们在本文中使用64作为SampleSize。我们选择建立模型的64个字符主要基于频率统计信息,并允许进行一些调整。在我们看来,某些字符(例如0x0d和0x0a)扮演的角色与空格字符(0x20)非常相似,因此已从采样中删除。其次,对于此64 x 64模型涵盖的所有序列,某些序列也与检测语言/编码无关..几乎所有单字节语言编码都将ASCII作为子集包含在内,通常会看到很多英文单词来自其他语言的数据,尤其是网站上的数据。同样明显的是,空-空序列与任何语言编码都没有关系。这些在我们的检测中被视为``噪声'',并通过过滤将其去除。第三,在计算置信度时,我们还需要计算落入样本范围的字符数和落入样本范围的字符数。如果少量样本数据中的大多数字符不属于我们的采样范围,则序列分布本身可能会给我们带来高价值,因为在这种情况下可能会发现很少的负序列。如果文本采用了所需的编码,则已经送入检测器的信号应落入采样范围。因此,需要根据此数字来调整从计数负序列中获得的置信度。总结一下:
对于许多单字节编码,所有代码点都相当均匀地使用。即使对于那些确实包含一些未使用的代码点的编码,那些未使用的代码点也很少在其他编码中使用,因此不适合进行编码检测。对于某些多字节编码,此方法可产生很好的效果并且非常有效。但是,由于某些多字节编码(例如EUC-CN和EUC-KR)共享几乎相同的编码点,因此使用这种方法很难区分这些编码。考虑到浏览器通常没有大量文本这一事实,我们必须求助于其他方法来决定编码。对于使用易识别的转义或移位序列的7位多字节编码(如ISO-2022-xx和HZ),此方法可产生令人满意的结果。总结一下,编码方案方法,
对于多字节编码,尤其是那些无法通过Code Scheme方法可靠处理的编码,字符分布提供了强大的帮助,而无需深入进行复杂的上下文分析。对于单字节编码,由于输入数据大小通常很小,并且可能的编码太多,因此除非在某些特殊情况下,否则不太可能产生良好的结果。由于在这种情况下2字符序列分布方法会带来非常好的检测结果,因此我们在单字节编码方面对此方法没有做进一步的研究。总结这些要点,字符分布方法
在2字符序列分布方法中,我们可以在检测语言/编码时使用更多的信息数据。即使使用非常小的数据样本,也能获得良好的结果。但是,由于使用序列而不是单词(由空格分隔),因此如果将矩阵应用于多字节语言,它将非常大。因此,这种方法:
我们想在字符集自动检测器中使用的语言/编码包括许多多字节和单字节编码。鉴于每种方法的不足,仅这三种方法都无法单独产生真正令人满意的结果。因此,可以同时使用两种编码方式的复合方法。2-Char Sequence Distribution方法用于所有单字节编码检测。代码方案方法用于UTF-8,ISO-2022-xx和HZ检测。在UTF-8检测中,对现有状态机进行了少量修改。在确定了几个多字节序列后,UTF-8检测器宣告成功。(详见Martin Duerst(1977))。编码方案和字符分配方法都用于主要的东亚字符编码,例如GB2312,Big5,EUC-TW,EUC-KR,Shift_JIS和EUC-JP。对于Shift_JIS和EUC-JP之类的日语编码,也可以使用2-Char序列分布方法。因为它们包含大量的平假名syallbary字符,其工作方式类似于单字节语言中的字母.2-Char序列分布该方法可以用较少的文字材料获得准确的结果。我们尝试了两种方法-一种使用2-Char分布方法,而另一种则没有。有一些网站包含很多汉字和片假名字符,但只有几个平假名字符。为了获得最佳结果,我们同时使用字符分布和2-CharDistribution方法进行日语编码检测。然后是如何将这三种检测方法一起使用的一个示例。最上面的控制模块(用于自动检测器)具有如下算法:Charset AutoDetection(InputText){if(InputText中的所有字符均为ASCII ){如果InputText包含ESC或~~ {{通过InputText调用ISO-2022和HZ检测器;如果其中之一成功,则返回该字符集,否则返回ASCII;}else返回ASCII;}否则如果(inputText的开始BOM){返回UCS2;}否则{调用所有多字节检测器和单字节检测器;最有信心的退货;}}}总结以上代码片段中的序列,
利用代码方案,字符分布和2字符序列分布方法来识别语言/编码的复合方法已被证明在我们的环境中非常有效。我们介绍了Unicode编码,多字节编码和单字节编码。这些是互联网上我们当前数字文本中的代表性编码。有理由相信,可以将该方法扩展为涵盖本文未介绍的其余编码。尽管目前在我们的检测结果中只需要编码信息,但在大多数情况下也可以识别语言。实际上,字符分布和2-Char分布方法都依赖于不同语言字符的特征分布模式。仅在UTF16和UTF8的情况下,才检测到编码,但语言仍然未知。但是即使在这种情况下,将来仍可以轻松地扩展这项工作以涵盖语言检测。此处概述的3种方法已在Netscape 6.1 PR1和更高版本中作为``全部检测''选项实现。我们希望我们在自动检测中的工作可以使用户摆脱处理字符编码菜单的繁琐操作的进一步解放。字符编码菜单(或其他编码菜单)与Internet客户端中的其他UI项有所不同它向普通用户公开了i18n后端的一部分。它的存在本身反映了当今的网页在语言/编码方面的混乱程度。我们希望提供良好的编码默认设置和通用的自动检测功能,将有助于减轻用户在网上冲浪时遇到的大多数编码问题。Web标准正朝着Unicode(尤其是UTF-8)作为默认编码的方向发展。我们希望其在网络上的使用逐步增加。由于越来越多的用户可以在浏览或阅读/发送消息时摆脱与编码有关的问题,而部分地归功于自动检测功能,因此这种转变不必太明显。这就是为什么我们提倡良好的自动检测功能和良好的默认编码设置的原因。适用于Internet客户。
我们的自动检测可以识别一种语言。编码确定是该确定的副产品。在当前的工作中,我们仅以俄语作为单字节实现的示例。要添加其他单字节语言/编码,我们需要每种语言的大量文本样本数据以及一定程度的语言知识/分析..我们目前使用脚本为该语言的所有编码生成语言模型。这项工作目前不在Mozilla源码中,但我们希望在不久的将来将其公开。当我们这样做时,我们希望具有以上资历的人在这一领域做出贡献。由于我们尚未测试许多单字节编码,因此在应用于其他语言/编码时,我们在此提出的模型可能需要进行微调,修改或什至重新设计。
由21个元音和19个辅音等40个音组成,元音与辅音类似汉语拼音的韵母和声母。
元音:ㅏ ㅓ ㅗ ㅜ ㅡ ㅣ ㅐ ㅔ ㅑ ㅕ ㅛ ㅠ ㅢ ㅒ ㅖ ㅘ ㅝ ㅚ ㅟ ㅙ ㅞ
辅音:ㄱ ㄷ ㅂ ㅅ ㅈ ㄲ ㄸ ㅃ ㅆ ㅉ ㅋ ㅌ ㅍ ㅊ ㄴ ㅁ ㄹ ㅎ ㅇ
一、元音(21个)
气流在口腔的通道上不受到阻碍而发出的音就是元音,共有21个。
元音按发音过程中是否改变嘴唇形状和舌头位置,而分为单元音和双元音。
1.单元音10个:
ㅏ ㅓ ㅗ ㅜ ㅡ ㅐ ㅔ ㅚ ㅟ
音标:
ㅏ [a] 、ㅓ [eo] 、ㅗ [o] 、ㅜ [u] 、ㅡ [eu] 、ㅣ [ i ]、ㅐ [ae] 、ㅔ [e] 、ㅚ [oe] 、ㅟ [wi]
2.双元音11个:
ㅑ ㅕ ㅛ ㅠ ㅒ ㅖ ㅘ ㅙ ㅝ ㅞ ㅢ
音标:
ㅑ [ya] 、ㅕ [yeo] 、ㅛ [yo] 、ㅠ [yu]、ㅒ [yae] 、ㅖ [ye] 、ㅘ [wa] 、ㅙ [wae] 、ㅝ [wo] 、ㅞ [we]、ㅢ [ui]
注意:
元音在韩语字典里的顺序:ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ
二、辅音(19个)
辅音是仿照人发声器官发音时的模样创造的,韩语辅音共有19个。
与元音一样,韩语中辅音根据发音方式的不同分为松音、紧音、送气音。
1.松音9个:
发音时比较自然,并伴有轻微气流流出。
ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅇ ㅈ
2.紧音5个:
发音时喉咙处于短暂地紧张状态,无明显气流喷出,并且用力强度和发音都比松音重。
ㄲ ㄸ ㅃ ㅆ ㅉ
3.送气音5个:
发音较轻,口中有明显气流发出并且气流强度比松音大。
ㅋ ㅌ ㅍ ㅊ ㅎ
注意:
辅音在韩语字典里的顺序:ㄱ ㄲ ㄴ ㄷ ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ