字符集与字符编码问题

字符集

ascii

基本学计算机的童鞋接触到字符串的时候都会学ascii编码,在这里不用特意去分字符集和编码的问题,因为此时ascii是字符集同时也规定了编码。
ascii实际上就是用一个字节8位去表示控制码,大小写英文字母以及阿拉伯数字,当时电脑还没有普及开,所以这种简单的甚至是简陋的方法得以正常使用,ascii的名字也可以看出当时的人的看法:American Standard Code for Information Interchange(美国信息互换标准代码)。
此时的ascii码只用到了低128位,最高位默认位0。然而低128位只用来编码英文字符没有问题,但是当电脑发展到其他一些欧洲国家或者采用拉丁字母的国家中,这些国家自己用的字母在ascii中是不存在的。于是各个国家开始自己给自己编码,为了保持对ascii的兼容,就在ascii的128到255编码范围内做文章。补充进来的被称为“扩展字符集”。
但是由于各个国家都编写自己的编码规则,导致不同国家的编码是不通用的,此时就已经开始出现一些编码导致的通信问题。但是当计算机传入中国的时候大问题才来了。

gb×系列

ascii是单字节编码,就算加上扩展字符集也只能编码256个字符,然而中国文化博大精深,即使是只考虑常用汉字也有六千多个。于是在ascii这种单字节编码上做文章是不大可能了,因为采用双字节编码,就是gb2312编码。

规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

双字节理论上可以编码65536种字符,但是由于为了解决当时燃眉之急,只收录了六千余个汉字,并且为了保持与ascii的兼容,就规定对于汉字的编码只使用一个字节的128到255的范围,低于128的范围保留用于与ascii兼容。然后台湾等地区由于常用繁体字,所以他们也有一套自己的编码规则Big5,即大五码,收录了一万三千多个汉子。
后来用着用着,发现在某些人名地名古汉字上,gb2312总是有不支持的地方,开始的时候用一些造字软件凑合,后来这种问题越来越多,于是大家就像干脆再出一套新的编码,这就是GBK。
GBK兼容gb2312,采用单双字节变长编码,英文使用单字节编码,完全兼容ASCII字符编码,中文部分采用双字节编码。GBK和gb2312都是采用双字节字符集(DBCS),扩展出来的原理是解除了gb2312对于低位字节的限制,GBK中只要求第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。
然后后来又有更大的扩展即gb18030,这个字符集基本涵盖了所有汉字,少数民族文字,采用四字节变长编码方式。这里就不细说了。

ANSI

ANSI不是特指某种编码,前面的GB系列中文编码实际上都是属于ANSI,但是ANSI也不止这些。
不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。

UCS(unicode)

但是各个国家民族都在自己的语言上开发编码,那么在计算机的领域内,交流的鸿沟就会越来越大,最好的方式就是一共有足够公信力的机构组织以全球的语言体系为基础开发一套字符集与对应的编码方案。
果不其然,这档子事是由ISO(国际标准化组织)来做的,他们重新做了一套”Universal Multiple-Octet Coded Character Set”,简称 UCS, 是一种定长编码方式,俗称 “UNICODE”,通过ISO10646标准发布。
UCS分为UCS-2和UCS-4,分别指使用两字节和四字节的编码方案。目前所有的编码都可以用UCS2涵盖,UCS如果前两字节是0x00,那么就成为UCS4的BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

字符编码

UTF

前面已经说了,字符集和字符编码是不同的东西,但是前面的ANSI系列的字符集和编码集合在使用方式上有着局部性,所以基本编码方式是唯一的。

UCS是一套比较完备的编码,规定了每个字符与一个特定字节的联系,但是这样会有一些问题。一是unicode字符串的长度和ANSI的长度的定义方式不同了。二是会有一定程度上空间的浪费。三是unicode字符串中会有零字节,但是在C中,\0表示字符串的结尾。
为了(在一定程度上)解决这些问题,是UCS能够有效地被使用起来,UTF(UCS Transformation Format)便应运而生。UTF是UNICODE的编码方案,也就是具体的实现,有很多版本。最常见的就是UTF-8,除了这个还有UTF-16,UTF-7等等。

UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。跟据下表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

Unicode符号范围 UTF-8编码方式
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

字节序与BOM

字节序实际上就是传输过程中高低位的顺序问题,常被称为大端小端顺序,也会被称为网络序和主机序。另外字节序位本机的存储中还会导致一些类型转换之类的细节问题,这里不细谈。
而unicode在存储的时候就会遇到这个问题。比如,“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们处理字符“594E”,那么这是“奎”还是“乙”?
为了解决这个问题,并且可以识别 Unicode 文件,Microsoft 建议所有的 Unicode 文件应该以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符开头。这作为一个“特征符”或“字节顺序标记(byte-order mark,BOM)”来识别文件中使用的编码和字节顺序。
在传输中,UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
In [38]: s="编码"

In [39]: s # 默认用utf-8
Out[39]: '\xe7\xbc\x96\xe7\xa0\x81'

In [40]: unicode(l.decode("utf8")) # 解码后用unicode编码(UCS2)
Out[40]: u'\u7f16\u7801'

In [41]: s.decode("utf8") # 从这个看出解码后默认用ucs字符集
Out[41]: u'\u7f16\u7801'

In [42]: s.decode("utf8").encode("gbk") # 用gbk编码
Out[42]: '\xb1\xe0\xc2\xeb'

In [43]: len(s) #看出各个编码的长度定义是不同的,utf8是统计字节数
Out[43]: 6

In [44]: len(s.decode("utf8")) # unicode(UCS2)每两字节算一个长度
Out[44]: 2

In [46]: len(s.decode("utf8").encode("gbk")) # gbk也是统计字节数,但是由于定长编码,一个字固定两字节。
Out[46]: 4

In [47]: us=u"编码"

In [48]: us
Out[48]: u'\u7f16\u7801'

解析编码

  • XML解析读取XML文档时,W3C定义了3条规则:

    1. 如果文档中有BOM,就定义了文件编码
    2. 如果文档中没有BOM,就查看XML声明中的编码属性
    3. 如果上述两者都没有,就假定XML文档采用UTF-8编码
  • 对于Unicode文本最标准的途径是检测文本最开头的几个字节。如:

    1. 开头字节时 EF BB BF————–UTF-8
    2. 开头字节时 FE FF——————UTF-16/UCS-2, little endian(UTF-16LE)
    3. 开头字节时 FF FE——————UTF-16/UCS-2, big endian(UTF-16BE)
    4. 开头字节时 FF FE 00 00———–UTF-32/UCS-4, little endian.
    5. 开头字节时 00 00 FE FF———–UTF-32/UCS-4, big-endia

其他编码

BASE64

是一种以可见ascii符号作为编码单元编码二进制的方式。
base64可以编码小幅图片或者音频等二进制文件,并且可以被文本编辑器打开。例如下图就是用base64编码的图片,可以在网页源代码中查看其编码内容。

除了base64之外还有base24/base32等等原理大同小异。

base58

Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址。相比Base64,Base58不使用数字”0”,字母大写”O”,字母大写”I”,和字母小写”l”,以及”+”和”/“符号。
这个编码应用于bitcoin中,用来可视化一些密钥和哈希值,为了更好的可视化,去掉了一些容易混淆的字符,毕竟和钱有关,防止粗心大意看错编码,也防止有些别有用心的人在这上面做文章。


参考内容:

  1. 各种字符编码方式详解及由来
  2. 阮一峰 字符编码笔记:ASCII,Unicode和UTF-8
  3. ANSI百度百科词条
  4. 程序员趣味读物:谈谈Unicode编码
本站总访问量