计算机字符储存与编码

数据存储单位

计算机数据存储的常见单位为 bit byte word KB MB GB TB 等额。
bit byte word 即位、字节、字。
各单位的换算规则为:


Bit = 1/0 (基本存储单位)

Byte = 8 bits

Word = 16, 32, 64 bits(取决于计算机位数)

$1 kilobyte = 1 KB = 2^{10} bytes = 1024 bytes$

$1 Megabyte = 1 MB = 2^{20} bytes = 1,048,576 bytes$

$1 Gigabyte = 1 GB = 2^{30} bytes = 1,073,741,824 bytes$


位是最小的存储单位,每一个位存储一个 1 位的二进制码。
一个字节由 8 位组成。
字通常为 16、32 或 64 个位组成,取决于计算机 CPU 一次性处理数据的位数,通常由寄存器决定。

字符集

字符收集

世界众多的文明创造除了庞大的文字,符号;
这些符号可以是英语的基本字母和标点,汉语的文字和标点,或者是其他语言的相关字符。
根据不同地区的使用需求,将不同的字符放在不同的集合形成众多的字符集。

常见的字符集有 Unicode、GBK、Big5 等。

字符集将需要的字符收集并规定了其中符号的二进制代码,供后续编码使用。
这个二进制代码通常被称为码点(Code Point)

Unicode

Unicode 是一个独立的字符集,每一个字符有一个对应的 Unicode 编号。
目前的 Unicode 字符分为 17 组编排,每组称为平面(Plane),而每平面拥有 65536(即 2 的 16 方)个码点。

计算机字符储存与编码20230216161718
目前只用了少数平面
第一平面的保留码点为 D800~0xDFFF,是特定为四字节的 UTF-16 编码预留的。

编码

给字符集编码

编码可以理解为选定字符集后,使用特定规则储存码点。

上文提到,字符集已经规定了每个字符对应的码点,那编码时直接将对应码点存储即可,
为何同一字符集会有不同的编码规则,如 Unicode 对应的 UTF-8/UTF-16/UTF-32?

为了传输和存储需要,同一字符集的对码点的编码的规则可以不一样。

可以采用为每个字符分配固定长度的内存的编码方案,即所有的码点都用固定位数存储,高位不足补零。
定长的好处是多个字符形成的数据编码放在一起也可以很好地截断然后解码。
但是,这种方式会造成码点小的字符内存空间的浪费。

也可以采用为每个字符分配尽量少的内存的方案,即码点对应实际需要占用的最小字节空间。
这种方案节省了内存,但是由于每个字符存储长度不定,计算机不知道如何截断多个字符编码以解码。
需要解决字符截断问题,然后才可以进行解码。

ASCII

美国信息交换标准码(American Standard Code for Information Interchange)
ASCII 码诞生于上世纪 60 年代,是最早的编码之一,共定义了 128 个字符(0-127),沿用至今。
这 128 个字符被设计用用电脑的 7 位来存储,但在计算机中占用 1 字节( 8 位)。
这种 8 位 的 2 进制编码,最高位始终为 0,其他 7 位用来表示字符

ASCII 的所有字符码点都只需要一个字节来存储。

UTF

  • UTF-8

Unicode 是一个字符集,如果全部按照 Unicode 中的码点来进行定长编码,程序定长截取字符,不会存在乱码问题。
但是,对于全英文文本文件,用 Unicode 编码比 ASCII 编码需要多一倍的存储空间,在存储和传输上就十分不划算。

​ 一种变长的编码方案由此诞生,该方案使用 1~6 个字节来存储码点,同时兼容 ASCII 码;
​ 变长编码首先需要解决的问题就是字符截断问题。

UTF-8 使用如下规则:
如果只有一个字节,那么最高的比特位为 0,其他位即实际的码点;
这种方式对码点编码得到的记过 ASCII 规则一致,故其兼容 ASCII 码。
如果是 n 字节,那么第一个字节前 n 个比特位的值为 1,n+1 为设为 0,剩下的字节(每 8 位)均以 10 开头,其他位为实际的码点。

单字节
0xxxxxxx

双字节
[110x xxxx] [10xx xxxx]

三字节
[1110 xxxx] [10xx xxxx] [10xx xxxx]

四字节
[1111 0xxx] [10xx xxxx] [10xx xxxx] [10xx xxxx]

上述’x’的位置纯纯的就是实际的码点。
如下示例:
在 nodejs 进行测试

Buffer.from('我')
// <Buffer e6 88 91>
String.fromCodePoint(0x6211)
// '我'
String.fromCodePoint(25105)
// '我'
'我' == '\u6211'
// truez
'我'.charCodeAt(0)
// 25105

可以看到,’我’在内存中存为:

;[0xe6][0x88][0x91]

其二进制形式:

[1110 0110] [1000 1000] [1001 0001]

按照 utf8 编码,显然 1110 表示该码点使用 3 个字节;
接下来的 2 个字节以 10 开头。
去掉这些编码标识,
“我”的实际二进制 0110 0010 0001 0001(Unicode 码点) ,十六进制 0x6211,十进制 25105

字符 UTF-8 二进制 UTF-8 对应十六进制 二进制 十进制 十六进制
[1110 0110] [1000 1000] [1001 0001] [0xe6] [0x88] [0x91] [0110 0010] [0001 0001] 25105 0x6211

  • UTF-16

介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。
UTF-16 的设计也存在读取问题,当遇到两个字节,怎么看出它本身是一个字符,还是需要跟其他两个字节放在一起解读。
UTF-16 将取基本平面预留段取两个码点组成新的码点,以解决读取问题;
一个辅助平面的字符,会被拆成两个基本平面的字符表示。

具体来说,UTF-16 使用的解决规则如下:
在基本平面(BMP),0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 码点。
在其他平面(辅助平面)对于 Unicode 码点范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储。
具体来说就是:将字符码点的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,称为高位(H);
较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储,称为低位(L)

UTF-16 最少使用两个字节来存储码点,故不兼容 ASCII。

  • UTF-32

一种固定长度的编码方案,不管字符码点大小,始终使用 4 个字节来存储;
UTF-32 是固定长度的编码,始终占用 4 个字节,容纳 Unicode 字符集已有所有的 字符,故直接存储 Unicode 码点。

其转换规则简单直观,解码效率高,但其比较浪费空间,故一般不使用这种编码方式,其也不兼容 ASCII。

BMW WARNING

  • Bulletin

本文首发于 skyline.show 欢迎访问。
文章实时更新,如果有什么错误或不严谨之处望请指出,十分感谢。
如果你觉得有用,欢迎到Github 仓库点亮 ⭐️

I am a bucolic migant worker but I never walk backwards.

  • Material

参考资料如下列出,部分引用可能遗漏或不可考,侵删。

https://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%B9%B3%E9%9D%A2%E6%98%A0%E5%B0%84

  • Warrant

本文作者: Skyline(lty)

文章链接:http://www.skyline.show/字符集与编码.html

授权声明: 本博客所有文章除特别声明外, 均采用 CC BY - NC - SA 3.0 协议。 转载请注明出处!

Copyright © 2017 - 2024 鹧鸪天 All Rights Reserved.

skyline 保留所有权利