ASCII、Unicode和UTF-8、UTF-16、UTF-32的关系

ASCII码

ASCII码表

计算机中任何信息都是用0和1来表示的。但是现实世界各种信息如图像、视频、音频、语言文字却不是用0和1表示的。为了计算机能够更加智能,我们需要让计算机能够识别人类世界的信息,所以我们需要将图像、视频、音频、语言文字等信息转换成二进制(0和1)存储到计算机中,这一过程也称之为编码。

编码是信息从一种形式或格式转换为另一种形式的过程。

  • 如果只有一位二进制,那么可以代表两种情况,即2^1=2,可能是0或1。
  • 如果有两位二进制,那么就可以代表四种情况,即2^2=4,可能是00、01、10或11。
  • ......
  • 如果有七位二进制,那么就可以代表2^7=128种情况。

在计算机中每八位二进制表示一个字节(Byte)。早些时候计算机用8位二进制来编码表示英文字母及一些特殊符号(最前面的一位是0,因为如果八位二进制可以表示2^8=256种情况,如果最前面一位是0那么就只能表示2^7=128种情况)。

例如数字字符'9'所代表的二进制是00111001,二进制转换成十进制是57,转换成十六进制是39。

例如字母字符'A'所代表的二进制是01000001,二进制转换成十进制是65,转换成十六进制是41。

也就是说ASCII码是一张码表,在这张表中规定了美国一些常用字符(包括数字字符、字母字符等)所对应的二进制数,它们是一一对应的(也对应了十进制数0到127)。

关于ASCII码表请参考:ASCII码表详解

扩展ASCII码表

这时候的ASCII码表仅仅只支持美国人的日常使用,其他国家人无法用ASCII码表表示他们国家的语言文字。

所以各个国家将字节(8位二进制)中最前面未被使用的一位拿来使用,用来表示自己国家的字符符号,那么用8位二进制来表示状态就有2^8=256种情况了。比如 é 就被编码成 130(二进制的 10000010)。

为了保持与ASCII码的兼容性,一般最高位为0的字节所表示的码与原来的ASCII码相同,只有当最高位为1(1xxx xxxx)的时候才表示各个国家给自己国家添加的128个特殊字符。

但问题来了,前面说的是各个国家对最高位为1之后的128个数字赋予了不同的含义,比如1111 0000这个数字在法国所表示一个字符,但在德国就表示另外一个字符。

可能有多少个国家,扩展ASCII码就有多少种,如果给一串二进制数,如果想要将其解码成字符,我们必须知道它是哪个国家的编码方式,否则就会出现乱码,也不会解码成我们想要的内容。

Unicode

再后来,发现这样不行,如果要对一串未知的二进制进行解码,你必须知道它的编码方式。

为了统一所有文字的编码,产生了Unicode,把所有语言的都统一到一套编码里,这样就不会乱码了。它为世界上的每个字符都分配了唯一的数字编号,可以这样说,Unicode是一张比ASCII更大的表,它包括了世界上所有国家的所有字符。

Unicode是为了解决传统字符编码方案的局限而产生的,为每种语言中的每个字符都设定了统一唯一的二进制编码,以实现跨语言、跨平台进行文本转换、处理的要求。

这个编号的范围是从0x000000到0x10FFFF,每个字符都有一个唯一的 Unicode 编号,这个编号一般写成 16 进制,在前面加上 U+。例如:“马”的 Unicode 是U+9A6C。

Unicode仅仅只是一个字符集,规定了符合对应的二进制代码,至于这个二进制代码如何存储则没有任何规定。它的想法很简单,就是为每个字符规定一个用来表示该字符的数字,仅此而已。

以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,这也就说明了它至少需要 2 个字节来表示。可以想象,在 Unicode 字典中往后的字符可能就需要 3 个字节或者 4 个字节,甚至更多字节来表示了。

这就导致了一些问题,计算机怎么知道你这个 2 个字节表示的是一个字符,而不是分别表示两个字符呢?这里我们可能会想到,那就取个最大的,假如 Unicode 中最大的字符用 4 字节就可以表示了,那么我们就将所有的字符都用 4 个字节来表示,不够的就往前面补 0。这样确实可以解决编码问题,但是却造成了空间的极大浪费,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无法接受的。

至于这唯一的数字编号如何对应到二进制表示,有很多种方案:主要有UTF-8、UTF-16、UTF-32。

UTF-32

UTF-32种的32表示32位二进制,四个字节,是直接将Unicode码转换成整数二进制形式。如“汉”字对应的二进制数是0110 1100 0100 1001。

计算机在存储器种排列字节有两种方式:大端法和小端法。

大端法就是将高位字节放在低地址处,如0x1234,计算机用两个字节存储,一个高位字节0x12,一个低位字节0x34。

UTF-32用四个字节来表示,如果不区分大小端的话,那么就会出现解读错误,例如拿到四个字节12 34 56 78,那么它到底表示的是0x12 34 56 78还是0x78 56 34 12呢,按照不同的端解释那么最后所表示的值不一样。

可以根据他们高低字节的存储位置来判断他们所代表的含义,所以在编码方式中有 UTF-32BE 和 UTF-32LE,分别对应大端和小端,来正确地解释多个字节(这里是四个字节)的含义。

UTF-16

UTF-16 使用变长字节表示 

① 对于编号在 U+0000 到 U+FFFF 的字符(常用字符集),直接用两个字节表示。 
② 编号在 U+10000 到 U+10FFFF 之间的字符,需要用四个字节表示。

同样,UTF-16 也有字节的顺序问题(大小端),所以就有 UTF-16BE 表示大端,UTF-16LE 表示小端。

UTF-8

UTF-8 是目前互联网上使用最广泛的一种 Unicode 编码方式,它的最大特点就是可变长。它可以使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。编码规则如下:

  • 对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。
  • 对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。

编码规则如下:

Unicode 十六进制码点范围UTF-8 二进制
0000 0000 - 0000 007F0xxxxxxx
0000 0080 - 0000 07FF110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

根据上面给出的编码规则,来解释下UTF-8为什么二进制是这样?

 下面以汉字"汉"为例,来说明如何进行UTF-8编码和解码。"汉"的Unicode码点是0x6c49,转换成二进制就是110 1100 0100 1001,通过对照表,发现0x0000 6c49位于第三行的范围。

 那么得出基本格式是"1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx",接着从"汉"字的二进制数最后一位开始,从后往前依次填充对应格式中的x,多出的x用0补上。这样最后得到的结果就是"汉"的UTF-8编码是11100110 10110001 10001001,转换成十六进制就是 0xE6 0xB7 0x89

解码的过程是:如果一个字节的第一位是0,那么表示这个字节对应一个字符;如果一个字节的第一位是1,那么连续有多少个1,就表示该字符占用多少个字节。

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

综上所述,UTF-8、UTF-16、UTF-32 都是 Unicode 的一种实现。

字节序

字节序也就是我们常说的大端和小端。

所谓BOM,全称是Byte Order Mark,它是一个Unicode字符,通常出现在文本的开头,用来标识字节序(Big/Little Endian),除此之外,还可以标识编码(UTF-8/UTF-16/UTF-32)。

对于UTF-8/16/32而言,它们名字中的8/16/32指的是编码单位是多少位的,也就是说,它们的编码单位分别是8/16/32位,换算成字节就是1/2/4字节,如果是多字节,就要牵扯到字节序,UTF-8以单字节为编码单位,所以不存在字节序。

UTF- 8编码的文件中,BOM占三个字节。这是个标识UTF-8编码文件的好办法,软件通过BOM来识别这个文件是否是UTF-8编码,很多软件还要求读入的文件必须带BOM。

UTF-8也有BOM,但是一般不建议带上,因为UTF-8没有大小端,BOM是一个固定的值。

Windows系的软件保存的文本文件,默认都带有BOM,处理的时候务必注意。

UTF编码Byte Order Mark(BOM)
UTF-8 without BOM
UTF-8 with BOMEF BB BF
UTF-16LEFF FE
UTF-16BEFE FF
UTF-32LEFF FE 00 00
UTF-32BE00 00 FE FF

我们自己来查看编码的BOM,创建一个test.txt文件,内容为"hello world"先设置编码为UTF-8(也就是UTF-8 without BOM),然后使用WinHex软件查看它的十六进制编码内容。

 查看UTF-8-BOM(也就是UTF-8 with BOM)的头

查看UTF-16LE的头

  查看UTF-16BE的头

 查看UTF-32LE的头

查看UTF-32BE的头

 UTF-16BEUTF-16LEUTF-16 三者之间的区别:

  • UTF-16BE,其后缀是BE,即big-endian,是大端的意思,即将高位的字节放在低地址表示。使用了该编码后的文件开头一定是FE FF。
  • UTF-16LE,其后缀是LE,即little-endian,是小端的意思,即将高位的字节放在高地址表示。使用了该编码后的文件开头一定是FF FE。
  • UTF-16,没有指定后缀,即不知道是大端还是小端,所以其开始的两个字节表示是大端还是小端,如果是FE FF表示大端,如果是FF FE表示小端,所以如果使用了UTF-16编码的文件具体是大端还是小端,由开头两个字节决定。

GBK

GB2312是对ASCII的中文扩展。

GBK编码:是指中国的中文字符,其它它包含了简体中文与繁体中文字符,另外还有一种字符“gb2312”,这种字符仅能存储简体中文字符。gbk是兼容gb2312编码的。

GBK编码格式,它的功能少,仅限于中文字符,当然它所占用的空间大小会随着它的功能而减少,打开网页的速度比较快。

ISO-8859-1

属于单字节编码,最多能表示的字符范围是0-255(因为一个字节有八位,即2^8=256),应用于英文系列。比如,字母a的编码为0x61=97。是20世纪80年代的一个传统标准,由于只能代表256个字符,因此只适用于西方世界的某些语言。即使对于许多支持的语言,也缺少一些字符,更无法表示中文字符。

但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

参考链接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值