0
点赞
收藏
分享

微信扫一扫

字符编码总结 (未完)

崭新的韭菜 2022-04-02 阅读 73
经验分享

历史

计算机只认识0或1,为了将人类认识的信息(字符)存储进计算机里,规定了一些转换的对应关系(字符集)

BCD码

BCD(BinaryCoded Decimal)

最初的计算机性能和存储容量都比较差,所以普遍采用4位BCD编码,将十进制用二进制表示
 BCD编码表示数字还可以,但表示字母或符号就很不好用,需要用多个编码来表示。后来经过演变发展成了ASCII码

ASCII字符集

American Standard Code for Information Interchange, 美国信息交换标准码

最初只使用了7位,称为基本ASCII码

0~31 之间的ASCII码常用于控制像打印机一样的外围设备,称为"控制码"

32~127 之间的ASCII码表示的符号,键盘上都可以被找到

后来增加第8位,扩展了128 个特殊符号字符、外来语字母和图形符号,从128 到255的字符集被称"扩展字符集"

中文编码_简略

拉丁语系国家的人使用是足够的,但是对于非拉丁语系国家(如中国、日本等),ASCII码就远远不够了

以下介绍几个中文使用的编码字符集(简略)

GB2312字符集

作用:国家简体中文字符集,兼容ASCII

位数:使用2个字节表示,能表示7445个符号,包括6763个汉字,几乎覆盖所有高频率汉字。

范围:高字节从A1-A7,低字节从A1到FE。将高字节和低字节分别加上0xA0即可得到编码。

GBK字符集

作用:它是GB2312的扩展,加入对繁体字的支持,兼容GB2312.

位数:使用2个字节表示,可表示21886个字符。

范围:高字节从81到FE,低字节从40到FE.

GB18030字符集

作用:它解决了中文、日文、朝鲜语等的编码,兼容GBK。

位数:它采用变字节表示(1ASCII, 2,4字节)。可表示27484个文字。

范围:1字节从00到7F;2字节高字节从81到FE,低字节从40到7E和80到FE;4字节第一三字节从81到FE,第二四字节从30到39

这一系列汉字编码通称叫做 "DBCS"(Double Byte Charecter Set 双字节字符集)

在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了

Unicode与UCS

当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码

有两个机构都选择去开始制定一个容纳全世界所有文字字符的字符集

(1) 国际标准化组织(ISO),他们于1984年创建ISO/IEC JTC1/SC2/WG2工作组,试图制定一份“通用字符集”(Universal Character Set,简称UCS),并最终制定了ISO 10646标准
(2) 统一码联盟,他们由Xerox、Apple等软件制造商于1988年组成,并且开发了Unicode标准(The Unicode Standard)

1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作

从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致

两个项目仍都独立存在,并独立地公布各自的标准。不过由于Unicode这一名字比较好记,因而它使用更为广泛

注:Unicode 就是给计算机中所有的字符各自分配一个数字,到目前为止,我们只是找到了一堆字符和数字之间的映射关系而已,只到了CCS的层次。这些数字如何在计算机和网络中存储和展示还没有提到。

为了理解下面的内容,一些必备知识(可先跳过):

字节和字符的区别

字节(octet)是一个八位的存储单元,取值范围一定是0~255

而字符(character,或者word)为语言意义上的符号,范围就不一定了,可能会使多个字节表示一个字符

Big Endian和Little Endian(大端模式和小端模式)

一、起源

来自于Jonathan Swift的《格利佛游记》:Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因:大家都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可以那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受

什么是字节序

如果一个数据大于127(2的8次方减1),就必须有两个字节或多个字节来放了。一个数字肯定是有高位和低位的,这个小学就有讲过,比如十进制数13,1就是高位,3就是低位。于是问题就出现了,计算机进行数据存取是先处理高位,再处理低位呢,还是处理低位,再处理高位?

不同的硬件架构可能使用不同的字节序

二、什么是大端和小端

举一个例子,比如数字0x12 34 56 78在内存中的表示形式。

1)大端模式:Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

            (其实大端模式才是我们直观上认为的模式,和字符串存储的模式差类似)

低地址 --------------------> 高地址
0x12  |  0x34  |  0x56  |  0x78

2)小端模式:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

低地址 --------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

  • 解决方案
    • 同意一种通用格式(即所有网络流量都遵循一种格式),或者
    • 始终包含描述数据格式的标题。如果标题向后出现,则表示数据以其他格式存储,需要转换。

注:不是所有的东西都有字节序,而且字符序是以单字节为单位的顺序问题,不是字节内部的

现代编码模型

在现代编码模型里要知道一个字符如何映射成计算机里比特,需要经过如下几个步骤:

  1. 知道一个系统需要支持哪些字符,这些字符的集合被称为字符表(Character repertoire)
  2. 给字符表里的抽象字符编上一个数字,也就是字符集合到一个整数集合的映射。这种映射称为编码字符集(CCS:Coded Character Set), unicode 是属于这一层的概念,unicode 跟计算机里的什么进制啊没有任何关系,它是完全数学的抽象的。
  3. 将 CCS 里字符对应的整数转换成有限长度的比特值,便于以后计算机使用一定长度的二进制形式表示该整数。这个对应关系被称为字符编码表(CEF:Character Encoding Form)UTF-8, UTF-16 都属于这层。
  4. 对于 CEF 得到的比特值具体如何在计算机中进行存储,传输。因为存在大端小端的问题,这就会跟具体的操作系统相关了。这种解决方案称为字符编码方案(CES:Character Encoding Scheme)。

code point(CCS)

将我们需要表示的字符表中的每个字符映射成一个数字,这个数字被称为相应字符的代码点(code point),代码点的值通常写成 U+ABCD 的格式(16进制)

Code Unit(CEF)

上述代码点是纯数字,为了能存入计算机,还需将这个数字用特定位数的二进制序列表示

代码单元是编码代码点的一部分的存储单元

一个或多个代码单元对单个代码点进行编码。每个代码单元具有相同的大小,这取决于所使用的编码格式,在 UTF-8 中这意味着 8 位,在 UTF-16 中这意味着 16 位

单个代码单元可以表示完整的代码点或代码点的一部分

Unicode细节

第一个版本:

Unicode最初只使用了0至65535之间的数字来表示所有字符(即两个字节)其中0至127这128个数字表示的字符仍然跟ASCII完全一样

第二个版本:

65536显然不算太多的数字,从1996年开始又来了第二个版本.用四个字节表示所有字符

Unicode目前的编码范围为0x0000~0x10FFFF,并将该范围分为17个平面,每个平面拥有65536(2^16)个码位,共1114112个,但是目前只用了少数的几个平面。其中最重要的一个平面为平面0(Basic Multilingual Plane,简称BMP),该平面的编码范围为0x0000~0xFFFF,包含了世界上常用的语言文字和符号,是Unicode最基础和常用的部分

注:

BMP中的字符如果用UTF-8编码,需要1-3个字节,UTF-16编码表示需要2个字节
BMP以外的字符的字符以UTF-8编码需要4个字节,UTF-16编码需要4个字节
另外还有一种UTF-32编码,该编码使用较少,UTF-32对任意Unicode字符编码都需要4个字节
BMP中0xD800~0xDFFF范围内共2048个码位没有对应任何字符,该范围称为Surrogate。该范围又分为两段,0xD800~0xDBFF包含的1024个码位称为high surroagte,0xDC00~0xDFFF包含的1024个码位成为low surroagte,可以通过这两个范围组成四个字节的surroagte pair来一一对应到BMP之外的字符

存储(unicode实现方案,对USC的方案UCS-2和UCS-4不做介绍)

将Unicode或者UCS规定的数字们以二进制的形式存储进计算机,即将代码点转换成一个或多个代码单元

对于每个代码点应该使用多大的空间呢

由于历史发展因素,或者考虑到存储以及读取效率,有了不同的实现方式

Unicode的UTF(unicode transformation format)

UTF-32就是把所有的字符都用32bit也就是4个字节来表示(不常用)

UTF-8可以选择1至4个字节中的任一个来表示,而UTF-16只能是选两字节或四字节..

UTF-8

UTF-8使用1~4个字节表示一个符号,不同的符号所占的字节数不同,所以UTF-8也称为可变长编码

此时程序是把一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把1个还是两个或三个字节做为一个单元来处理

UTF-8 的编码规则:

① 对于单字节的符号,字节的第一位设为 0,后面的7位为这个符号的 Unicode 码,因此对于小于0x7F的字符,如英文字母,UTF-8 编码和 ASCII 码是相同的。

② 对于n字节的符号 (n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10,剩下的没有提及的二进制位,全部为这个符号的 Unicode 码 。

1) 对于Unicode码值在0x0~0x7F(含两端)的, UTF-8编码直接就是它Unicode码值,即一个开头为0的字节.此时UTF-8编码与ASCII编码相同.

2) Unicode码值为0x80~0x7FF的, 编码为两个字节,形式是

110xxxxx 10xxxxxx.

3) Unicode码值为0x800~0xFFFF的,编码为三个字节,形式是

1110xxxx 10xxxxxx 10xxxxxx.

大部分汉字在这一段的子区间(0x4E00~0x9FFF), 因此用正则表达式匹配汉字时,可以利用这一点,比如VBScript的正则表达式用[\u4E00-\u9FFF]+作为模式.

4) Unicode码值为0x10000~0x10FFFF,编码为四个字节,形式是

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Unicode → UTF-8变换方式:

如何将Unicode码值转换为UTF-8编码呢? 先看码值在上述四个区间的那一个. 再将码值转换成二进制,将二进制值从右到左填入上面的样板的x的位置上, 最后如果还有x没有填满,则用0补满.

比如, 上面的"马"字的Unicode码值为9A 6C,先判断一下它处于上面第3种情况, 应该编码为3个字节. 模板是

1110xxxx 10xxxxxx 10xxxxxx

按每个字符转化为4个二进制位处理, 得到 1001 1010 0110 1100. 去除空间后将这一串二进制从右到左但顺序不变的往上面的模子里填,就得到了

11101001 10101001 10101100

你可以看到,从右往左的填入模板时, 0110被拆分了.这里一定要强调这个从右至左的顺序. 如果最后还有x没有填完,应该填入0替换掉所有x, 而不能不填!

然后,如果你对十六进字符与二进制的换算还熟悉,上面的二进制可以转换成6位的十六进制:

E9 A9 AC

这正是"马"字的UTF-8编码.

由于 UTF-8 的处理单元为一个字节(也就是一次处理一个字节),所以处理器在处理的时候就不需要考虑这一个字节的存储是在高位还是在低位,直接拿到这个字节进行处理就行了,因为大小端是针对大于一个字节的数的存储问题而言的.

例,“中”的unicode码为4E2D(‭100111000101101‬)属于上表中的第三行范围,所以”中”的UTF-8编码应该占3个字节(1110xxxx 10xxxxxx 10xxxxxx),用“中”的unicode码的二进制位进行填充,不足的位用0补齐。所以“中”的UTF-8编码为‭E4 B8 AD‬(11100100 10111000 10101101)

UTF-16

UTF-16为Unicode的另一种编码方式

位于BMP(范围0x0000~0xFFFF)中的符号,UTF-16编码可直接使用该字符的unicode码直接表示,这时UTF-16编码占2个字节

位于BMP外的符号,如果需要用UTF-16编码表示,需要将unicode码转换为四个字节的代理对(surroagte pair)来表示(具体细节略)

因为UTF-16采用两个字节为一个code unit,所以它具有字节序问题

UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。

(事实上我不知道这些编码单元大小设置是依据什么,而且即使cpu一次只处理utf-8的8bit,难道就不需要考虑这个字节的信息是整个字符编码的高位还是低位吗?毕竟utf-8可变长,可能不止一个字节啊,在网上我并没有找到答案)

UTF-32

UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符

附:

对比UTF-8UTF-16UTF-32UCS-2UCS-4
编码空间0-10FFFF0-10FFFF0-10FFFF0-FFFF0-7FFFFFFF
最少编码字节数12424
最多编码字节数44424
是否依赖字节序

关于计算机判断:

UTF-16需要通过在文件开头以名为BOM(Byte Order Mark)的字符来表明文件是Big Endian还是Little Endian

 在UCS 编码中有一个叫做 "Zero Width No-Break Space" ,中文译名作“零宽无间断间隔”的字符,它的编码是 FEFF。而 FEFF 在 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编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。

字符U+FEFF如果出现在字节流的开头,则用来标识该字节流的字节序,是高位在前还是低位在前。如果它出现在字节流的中间,则表达零宽度非换行空格的意义,用户看起来就是一个空格。从Unicode3.2开始,U+FEFF只能出现在字节流的开头,只能用于标识字节序,就如它的名称——字节序标记——所表示的一样;除此以外的用法已被舍弃。取而代之的是,使用U+2060来表达零宽度无断空白

举报

相关推荐

0 条评论