字符集和编码

字符集和编码

计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态。如果将这每一个状态对应一个符号,就是256个符号,从00000000到11111111。

ASCII 码

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。

ASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号,从0开始的32种状态分别规定了特殊的用途),只占用了一个字节的后面7位,最前面的一位统一规定为0。

非 ASCII 编码

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。

GB(中文)

汉字编码中现在主要用到的有三类,包括GBK,GB2312和Big5。

GB2312又称国标码,由国家标准总局发布,1981年5月1日实施,通行于大陆。新加坡等地也使用此编码。它是一个简化字的编码规范,当然也包括其他的符号、字母、日文假名等,共7445个图形字符,其中汉字占6763个。我们平时说6768个汉字,实际上里边有5个编码为空白,所以总共有6763个汉字。

GB2312规定“对任意一个图形字符都采用两个字节表示,每个字节均采用七位编码表示”,习惯上称第一个字节为“高字节”,第二个字节为“低字节”。GB2312中汉字的编码范围为,第一字节0xB0-0xF7(对应十进制为176-247),第二个字节0xA0-0xFE(对应十进制为160-254)。

GB2312将代码表分为94个区,对应第一字节(0xa1-0xfe);每个区94个位(0xa1-0xfe),对应第二字节,两个字节的值分别为区号值和位号值加32(2OH),因此也称为区位码。01-09区为符号、数字区,16-87区为汉字区(0xb0-0xf7),10-15区、88-94区是有待进一步标准化的空白区。

2、Big5又称大五码,主要为香港与台湾使用,即是一个繁体字编码。每个汉字由两个字节构成,第一个字节的范围从0X81-0XFE(即129-255),共126种。第二个字节的范围不连续,分别为0X40-0X7E(即64-126),0XA1-0XFE(即161-254),共157种。

3、GBK是GB2312的扩展,是向上兼容的,因此GB2312中的汉字的编码与GBK中汉字的相同。GBK中每个汉字仍然包含两个字节,第一个字节的范围是0x81-0xFE(即129-254),第二个字节的范围是0x40-0xFE(即64-254)。GBK中有码位23940个,包含汉字21003个。

unicode

不同地区都试图设计对自己地区语言支持最好的编码方案。如此以来,全世界出现了上百种编码方案。这产生了很大的问题。按照我自己的理解,我认为问题是这样的。在使用不同编码的系统之间,都需要进行编码的转换。然而由于每种编码都只能表示全世界中的某些语言的字符,因而在从编码A转换为编码B的过程中,就会出现编码A的某个字符不能用编码B表示,因此无法转换的错误。必须小心的检查数据才能避免这个问题。除此以外,对软件开发者而言,开发支持多种语言的软件也变得非常复杂,因此开发者会倾向于开发只支持一种语言的程序。这影响了软件在全世界的使用。

Unicode的第一个版本诞生于1991年。Unicode的最特别之处在于,打破了自电报时代起所固有的传统思路,即每个字符必须直接和一串比特序列进行对应。在这样的直接对应关系中间增加了一个新的层次:码点(code point),这是一个整数,每个字符都有唯一的一个整数与之对应。因此,字符首先对应到码点,然后码点再和对应的二进制编码对应。

简略的来说,Unicode编码的内容其实很简单:第一步,对于全世界需要编码的字符,每个字符分配一个整数(码点)作为字符的ID。第二步,将码点对应到二进制串上。Unicode定义的码点的取值范围从 0 到 10FFFF(16进制),这个范围被称为code space(编码空间)。根据编码空间的大小,可以确定至少需要21比特才能编码所有的Unicode字符。在Unicode标准中,定义了3种编码方案,分别被称为:UTF-8、UTF-16和UTF-32。

UTF是 Unicode Transformation Format 的简称。意思是,Unicode虽然定义了每个字符到一个整数的映射,但是这个整数怎么存储、传输和编码就在UTF中进行定义。在UTF-8中,用8bit作为一个基本的code unit。每个字符的编码长度,都会是code unit的整数倍。因此UTF-8中,字符的编码可能是8比特、16比特、24比特或者32比特。同理,UTF-16中code unit 长度是16比特,可能的编码长度是16比特或者32比特。UTF-32中code unit 长度是32比特,所有字符的编码都是32比特。

utf-8

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
码点 Unicode符号范围 (十六进制) UTF-8编码方式(二进制)
U+0000-U+007F 0000 0000-0000 007F 0xxxxxxx
U+0080-U+07FF 0000 0080-0000 07FF 110xxxxx 10xxxxxx
U+0800-U+FFFF 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+10000-U+1FFFF 0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

上图x表示用于存储码点数据的位。

汉字严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

UTF-8的每个code unit都是一个字节,因此没有大小端存储问题。在UTF-16中,一个code unit 大小两个字节,其中一个字节存储高位数据,另一个存储低位数据。当存储到内存或者磁盘时,是存储高位数据的字节在前还是存储低位数据的字节在前,分别对应了大端和小端两种存储方式。

大小端 & BOM 字符

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

BOM的英文全称是”byte order mark”,它是Unicode字符编码方案中的一个特殊字符,它是作为辅助之用的字符,而不是一个可打印字符。BOM指代的字节序列如果出现在字节流的最前面,它有以下几个作用:

  1. 表明编码得到字节流采用的是大端序,还是小端序。一个Unicode字符可以使用1个字节,2个字节或者4个字节进行编码,在采用2个字节或者4个字节进行编码时,又可以使用大端序或者小端序,在一个处理程序接收到这样的字节流后,可以通过字节流的最前面的字节序列,即BOM字符的字节序列采用的是大端序还是小端序,来获知编码得到该字节流时使用的是大端序还是小端序。
  2. 表明编码得到字节流有很大可能性采用了Unicode编码方案。
  3. 表明具体采用哪个Unicode编码方案。在不同的Unicode编码方案中,BOM字符对应的字节序列不一样,基于该点,根据获得的BOM字符的字节序列,可以推测出所采用的Unicode编码方案。
  • 在UTF-8中,BOM字符对应的字节序列是0xef,0xbb,0xbf。在UTF-8中,不推荐使用BOM字符,因为在使用UTF-8编码方案编码得到的字节流中,很容易就可以根据一些字符对应的特殊的字节序列而检测出所使用的编码方案是UTF-8。UTF-8一直使用大端序,不使用小端序。
  • 在UTF-16中,BOM字符对应的字节序列是0xfe 0xff(小端序)或0xff 0xfe(大端序)。
  • 在UTF-32中,BOM字符对应的字节序列是0x00 0x00 0xfe 0xff(小端序),或0xff 0xfe 0x00 0x00(大端序)。

UTF-16

Unicode的范围为U+0000~U+10FFFF,U+0000~U+FFFF这段区间,正好16位就可以表示.U+10000~U+10FFFF这段区间共有1048576个码位。这部分码位只有使用32位才能存储,在UTF16中被映射成一个代理对。一个代理对包含一个高位代理编码单元和一个低位代理编码单元,都是16比特的。每个16位各存一半(√1048576=1024)。32位二进制数字中,前后16位中各存10位就够用了。剩余的6位用来存放标志位。高位代理的范围是U+D800~U+DBFF,转换成二进制,它的格式应该是1101 10xx xxxx xxxx,低位代理的范围是U+DC00~U+DFFF,转换成二进制,它的格式应该是1101 11xx xxxx xxxx。

为了配合UTF-16,Unicode中也将上述两个代理位区间(即区间D800~DFFF)屏蔽掉,不允许分配任何字符。防止出现单16位和双16位的混淆。

以字符𐌂(Old Italic Letter Ke)为例,它的码位是U+10302,二进制表示是0000 0001 0000 0011 0000 0010,减去U+10000(二进制为0000 0001 0000 0000 0000 0000),得到0000 0000 0000 0011 0000 0010。从右到左填充进模版,得到1101 1000 0000 0000 1101 1111 0000 0010,对应的十六进制是D800 DF02。

UTF-16 编码有大小端之别,即 UTF-16BE 和 UTF-16LE,在每一个文件的最前面分别加入一个表示编码顺序的字符,一个 U+FEFF 或 U+FFFE(UTF-16BE 以 FEFF 代表,UTF-16LE 以 FFFE 代表),其中 U+FEFF 字符在 Unicode 中代表的意义是 ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。

UTF-32

UTF-32将每个Unicode标量值映射成一个无符号的32比特的编码单元,数值与Unicode标量值相同,这是一种定长的编码方案。

注意,UTF-32无法编码U+D800~U+DFFF之间的码位,因为它们不属于Unicode标量值。

-------------本文结束感谢您的阅读-------------
坚持分享,您的支持将鼓励我继续创作!
0%