基础
ASCII (American Standard Code for Information Interchange),1967年
GB2312 是中华人民共和国国家汉字信息交换用编码,国家标准总局发布,1981年5月1日实施,对应的国标标准号:GB/T 2312-1980
Unicode 1990年开始研发,1994年发布初版
其实还有比如日本 Shift-JIS,韩国EUC-KR
各式各样的编码,其实原理就是把用到的符号定义一个编号。
背景
在文件流读写的时候,对字节流和字符流的一个匹配关系产生了兴趣,比如一个文本文件存储结构是什么,为什么文件会乱码等一系列探索做个记录
正文
1. ASCII码
ASCII码,实际范围为(0x00 到 0x7F)【00000000~01111111】0~127,已被国际标准化组织ISO采纳,作为国际通用的信息交换标准代码。
在计算机内部, 所有的信息最终都表示为一个二进制的字符串. 每一个二进制位(bit)有0和1两种状态, 因此八个二进制位就可以组合出 256种状态, 这被称为一个字节(byte). 也就是说, 一个字节一共可以用来表示256种不同的状态, 每一个状态对应一个符号, 就是256个符号, 从 0000000到11111111.上个世纪60年代, 美国制定了一套字符编码, 对英语字符与二进制位之间的关系, 做了统一规定. 这被称为ASCII码, 一直沿用至今.ASCII码一共规定了128个字符的编码, 这128个符号(包括32个不能打印出来的控制符号), 只占用了一个字节的后面7位, 最前面的1位统一规定为0.
其中:
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符)
32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。
65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
ASCII码表具体情况可以参考
ASCII缺点
ASCII的局限在于只能显示26个基本拉丁字母、阿拉伯数字和英式标点符号。因此,现在的软件系统大多采用Unicode,特别是与ASCII向下兼容的UTF-8
对于亚洲国家的文字, 使用的符号就更多了, 汉字就多达10万左右. 一个字节只能表示256种符号, 肯定是不够的, 就必须使用多个字节表达一个符号. 比如, 简体中文常见的编码方式是GB2312, 使用两个字节表示一个汉字, 所以理论上最多可以表示256x256=65536个符号.
2. Unicode
Unicode官网:https://home.unicode.org/
2.1 Unicode的定义
Unicode的范围为 【0-0x10FFFF】,换算成10进制为【0-1114111】有0-1,114,112个字符,所以100多万个字符是足以支持世界上的任何语言的
世界上存在着多种编码方式, 同一个二进制数字可以被解释成不同的符号. 因此, 要想打开一个文本文件, 就必须知道它的编码方式, 否则用错误的编码方式解读, 就会出现乱码.为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样.
可以想象, 如果有一种编码, 将世界上所有的符号都纳入其中. 每一个符号都给予一个独一无二的编码, 那么乱码问题就会消失. 这就是Unicode, 就像它的名字都表示的, 这是一种所有符号的编码.
Unicode也是一种字符编码方法, 不过它是由国际组织设计, 可以容纳全世界所有语言文字的编码方案. Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS. UCS可以看作是"Unicode Character Set"的缩写.
Unicode当然是一个很大的集合, 现在的规模可以容纳100多万个符号. 每个符号的编码都不一样, 比如, U+0639表示阿拉伯字母Ain, U+0041表示英语的大写字母A, U+4E2D表示汉字中国的"中". 具体的符号对应表, 可以查询unicode.org, 或者专门的汉字对应表.
2.2 Unicode的问题
需要注意的是, "Unicode只是一个符号集, 它只规定了符号的二进制代码, 没规定这个二进制代码应该如何存储"。
比如, 汉字"中"的unicode是十六进制数4E2D, 转换成二进制数足足有15位(100111000101101), 也就是说这个符号的表示至少需要2个字节. 表示其他更大的符号,可能需要3个字节或者4个字节, 甚至更多。
这里就有两个问题,
第一个问题是, 如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号, 而不是分别表示三个符号呢?
第二个问题是, 我们已经知道,英文字母只用一个字节表示就够了, 如果unicode统一规定, 每个符号用三个或四个字节表示, 那么每个英文字母前都必然有二到三个字节是0, 这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍, 这是无法接受的.
它们造成的结果是:
1) 出现了unicode的多种存储方式, 也就是说有许多种不同的二进制格式,可以用来表示unicode.
2) unicode在很长一段时间内无法推广, 直到互联网的出现
注意:Unicode只是一个符号集,它只规定了各个字符所对应的二进制数,并不是为了计算机系统专门设计的,没有规定在计算机中如何存储。
2.3 UCS(Unicode的定长字符编码)
【这部分参考了:https://blog.csdn.net/qq_52102933/article/details/126595077】
通用多八位编码字符集(Universal Multiple-Octet Coded Character Set)也叫通用字符集(Universal Character Set, UCS),是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。
通用多八位编码字符集包括了其他所有字符集。它保证了与其他字符集的双向兼容,即,如果你将任何文本字符串翻译到UCS格式,然后再翻译回原编码,你不会丢失任何信息。
UCS-2
定长2个字节,这个不等于UTF-16
UCS-2(Universal Character Set coded in 2 octets),是用定长2个字节来表示字符(定长编码,ASCII码部分的字符存储也是2个字节),其取值范围为 U+0000~U+FFFF。Unicode当前默认的版本是UCS-2,UCS-2 编码 与 Unicode码 完全一样,6w+的字符量已经足以用于全球的主要语言的大多数字符。
可以参考以下截图:中(4e2d)国(56fd)A(0041)a(0061)
UCS-4
定长4个字节,这个等同于UTF-32,但这种我实践过程中是很少用,因为最少需要4个字节存储一个符号,比如一个字母A就需要4个字节存储(原本一个字节就够了),会特别浪费资源。
UCS-4,用四个字节表示代码点,最高位为0,取值范围为 (U+00000000~U+7FFFFFFF),允许表示一百多万个字符
比如,英文字母“A”对应的Unicode(十六进制)是U+0041,转换为十进制是65,转换为二进制是0100 0001,和ASCII码一致,只需要一个字节表示。
比如,中文“一”对应的Unicode(十六进制)是U+4E00,转换为十进制是19968,转换为二进制是100 1110 0000 0000,这个二进制有15位,需要至少2个字节表示。
具体的符号对应表,可以查询Unicode官网,也可以查询专门的汉字对应表,还有字符与Unicode编码在线转换。
3. UTF-8
UTF-8最大的一个特点, 就是它是一种变长的编码方式. 它可以使用1~4个字节表示一个符号, 根据不同的符号而变化字节长度。
互联网的普及, 强烈要求出现一种统一的编码方式. UTF-8就是在互联网上使用最广的一种unicode的实现方式. 其他实现方式还包括UTF-16和UTF-32, 不过在互联网上基本不用.重复一遍, 这里的关系是, UTF-8是Unicode的实现方式之一。
对比 | UTF-8 | UTF-16 | UTF-32 | UCS-2 | UCS-4 |
---|---|---|---|---|---|
编码空间 | 0 ~ 10FFFF | 0 ~ 10FFFF | 0 ~ 10FFFF | 0 ~ FFFF | 0 ~ 7FFFFFFF |
最少编码字节数 | 1 | 2 | 4 | 2 | 4 |
最多编码字节数 | 4 | 4 | 4 | 2 | 4 |
是否依赖字节序 | 否 | 是 | 是 | 是 | 是 |
3.1 UTF-8的编码规则
UTF-8的编码规则很简单, 只有两条:
1) 对于单字节的符号, 字节的第一位设为0, 后面7位为这个符号的unicode码. 因此对于英语字母, UTF-8编码和ASCII码是相同的。
2) 对于n字节的符号(n>1), 第一个字节的前n位都设为1, 第n+1位设为0, 后面字节的前两位一律设为10. 剩下的没有提及的二进制位, 全部为这个符号的unicode码。
下表总结了编码规则, 字母x表示可用编码位
下面, 还是以汉字"中"为例, 演示如何实现UTF-8编码.
已知"中"的unicode是4E2D(100111000101101), 根据上表, 可以发现4E2D处在第三行的范围内(0000 0800 - 0000 FFFF), 因此"中"的UTF-8编码需要三个字节, 即格式是"1110xxxx 10xxxxxx 10xxxxxx". 然后,从"中"的最后一个二进制位开始, 依次从后向前填入格式中的x, 多出的位补0. 这样就得到了, "中"的UTF-8编码是 "11100100 10111000 10101101", 转换成十六进制就是E4B8AD.
4. Little endian和Big endian
Unicode码可以采用UCS-2格式直接存储. 以汉字"中"为例, Unicode码是4E2D, 需要用两个字节存储, 一个字节是4E, 另一个字节是2D. 存储的时候, 4E在前,2D在后, 就是Big endian方式; 2D在前, 4E在后, 就是Littleendian方式.// Big Endian(4E2D) Little Endian(2D4E)
因此, 第一个字节在前, 就是"大头方式"(Big endian), 第二个字节在前就是"小头方式"(Little endian)。
4.1 计算机如何判定是哪一种编码?(零宽度非换行空格(FEFF)),这个也就BOM
Unicode规范中定义, 每一个文件的最前面分别加入一个表示编码顺序的字符, 这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE), 用FEFF表示. 这正好是两个字节, 而且FF比FE大1.// Big Endian(FEFF),Little Endian(FFFE)
如果一个文本文件的头两个字节是FE FF, 就表示该文件采用大头方式; 如果头两个字节是FF FE, 就表示该文件采用小头方式。
此处使用Notepad++进行演示
以二进制方式打开效果(插件里有一个HEX-Editor可以查看二进制):
5. Unicode与UTF-8之间的转换(javascript)
6.思考探索
我在用python获取文件的编码格式,发现没有那种一个属性就可以获取到这个编码格式,其实这个就可以理解什么是BOM 字节顺序标记(Byte Order Mark),通过通过占用几个字符来标记编码格式
import chardet
def get_file_encoding(file_path):
with open(file_path, 'rb') as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding']
return encoding
举例,可以看到我文件中有两个字符,实际文件大小只有2个字节,也就是这个文件中其实是没有地方去记录自己是用的是UTF-8还是GB2312的
然后我使用UTF-8 BOM格式存储,前面放置了3个字符用来标记 自己是UTF-8的格式
总结
如非特别指定,建议还是使用UTF-8编码,毕竟浏览器的网络流已经指定默认的编码就是UTF-8,一般系统默认了UTF-8,减少因编码带来的排障