在我们使用计算机处理文本文件时,有没有想过文件内容里都有什么?是不是有英文、中文、emoji 表情等等, 再进一步追问这些内容又是通过什么方式存储的?这里就需要引入字符集和字符编码的概念。
字符集和字符编码
ASCII
起初计算机发展于西方,需要保存的内容主要以拉丁字母为主。 计算机中信息是以字节的形式存储于文件系统中,便制定了 ASCII 标准对这些字符进行编码、存储和传输。 这些字符的个数不足 256 个,只需要 1 个字节即可表示,比如数字 0 对应的 ASCII 是 0x30。
GBK
随着计算机在世界范围内发展,越来越多其他语言的文字需要传输存储,ASCII 编码便无能为力了,因为 根本没有为其他字符保留编码定义。于是,各国纷纷根据本国的字符制定字符集和编码方式, 比如中国的字符集需要包含大量的汉字(简体、繁体)、中文符号等等,同时也需要制定每个汉字怎样存储, 于是诞生了 GBK ,便是对这些字符集的标准定义和存储方案设计,细节参考 Wiki。
Unicode
与此同时,其他国家也在发展自己的字符集和编码方式,但都是以本国的语言为主,导致信息在世界范围 内传递存在困难,于是一个国际标准 Unicode 字符集便出现了,定义了每个字符对应的码位(code point)。
到目前为止,Unicode 中的字符个数已经达到了 10w+,为了能表示足够多的字符,至少需要 3 个字节。 那是不是也像 ASCII 一样字符按 3 字节存储?这样并不合理,因为不同字符在计算机世界里出现的 频率是不相同的,比如常出现的英文、中文等本来可以用 1-2 个字节存储传输,现在需要 3 个字节, 无端增加了存储传输成本,所以需要一种合理的编码方案,将码位映射到对应的字节信息,这就是编码方式。 对于 Unicode 存在 UTF-8、UTF-16LE、UTF-16BE 等编码方案,涉及变长编码和字节顺序的问题,细节参考 wiki。
以下表格汇总了不同的字符集和编码方式:
字符集合 | 编码方式 |
---|---|
拉丁字母等 | ASCII |
中文等 | GBK |
Unicode | UTF-8 |
再举例一些字符不同的编码:
字符 | 类型 | Unicode | GB- | UTF-8 |
---|---|---|---|---|
A | ASCII 字符 | U+0041 | 41 | 41 |
一 | CJK 字符 | U+4E00 | D2BB | E4 B8 80 |
笑哭 | Emoji 字符 | U+1F602 | 94 39 FC 38 | F0 9F 98 82 |
程序编码
通过上面的信息,我们知道了不同的内容是如何存储、表示了,下一个问题是计算机程序是如何处理不同编码文件的?大概分为以下几个步骤:
磁盘文件 =>(读取) 二进制数据 => (编码方式识别及转码) 内码数据 => (字体编码) 屏幕显示
- 首先,程序通过IO接口将文件的二进制文件读入,
- 其次,按指定的编码方式或者自适应的编码方式来解码文件内容,
- 第三,如果上述过程顺利,将内容按程序内部运行需要的内码编码进行转码,
- 最后,如果需要显示,则识别对应的字符所需要的字体信息展示到屏幕上。
其中第二步是大家经常遇到乱码的环节,即没有使用对应的编码格式打开文件,下面以两个 常用的应用举例。
PYTHON
通过 sys.getdefaultencoding()
获取 Python 默认的编码方式,如果是 ASCII 码而且
代码文件中出现中文,则执行的时候回报错:
Non-ASCII character '\xe4' in file demo.py on line 1, but no encoding declared;
文件头中指定 # encoding:utf-8
,同时文件的存储改为 UTF-8
,
Python 则可以按 UTF-8
编码文中的内容。如果文件头指定了 UTF-8
,文件存储为 GBK
,
Python 运行依然出错,原因是 UTF-8
与 GBK
不兼容,无法识别按 GBK 编码方式存储的字符。
Python 解码字节流用的方法是 decode
,第一个参数为被解码的字节流原始编码格式,
需要根据存储编码格式填写,比如文件内容为 UTF-8
存储,用 GBK
解码会报错。
s = '中文'
z = s.encode('utf8').decode('GBK') # error
VIM
Vim 里面的编码主要和三个参数有关:enc(encoding),fenc(fileencoding) 和 fencs(fileencodings)
fenc 是当前文件的编码,一个在 Vim 里正确显示的文件(前提是系统环境跟enc设置匹配)。
可以通过改变 fenc 将此文件保存成不同的编码,比如:set fenc=utf-8
之后:w
把文件存成 UTF-8。
fencs 是用来在打开文件的时候进行解码的猜测列表。Vim 默认的编码猜测列表比较少,可能不能识别中文,所以需要添加对应编码方式, 顺序是以从前往后尝试,所以一定要把要求严格的编码方式放在前面,把宽松的编码方式放在后面。vimrc 参考配置如下:
set fileencodings=ucs-bom,utf-8,cp936,gb18030,latin1
enc 是 Vim 内部使用的字符编码方式。当我们设置了 encoding 之后,Vim 内部所有的 buffer、寄存器、脚本中的字符串等,全都使用这个编码。 Vim 在工作的时候,如果编码方式与它的内部编码不一致,它会先把编码转换成内部编码。enc 需要与系统编码一致,能在系统里正确地显示出来。
- Windows 中文系统中默认内码为 cp936,可以通过 chcp 命令查看,enc 默认是 cp936 不需要改;
- Linux下,随着系统 locale 可能设为 zh_CN.utf-8,enc 要对应的设为 utf-8。
相关工具
下面介绍一些与编码相关的工具。
vim 编码修改
如果已经用错解码方式打开了文件,可重新设置编码格式:
:edit ++enc=utf8
bash 转换编码
iconv -f gbk -t utf8 file1 -o file2 # 将一个GBK 编码的文件转换成UTF-8编码
UTF-8 vs UTF-8 BOM
在 Windows 系统中 UTF-8 文件可能多增加了一个 BOM(byte-order mark),即字节顺序标记。 使用 Vim 打开的时候,文件开头会有 3 个字节,而 Linux 系统中一般没有,我们可以统一按没有使用。
sed -i '1s/^\xEF\xBB\xBF//' orig.txt
使用上述命令可以将 BOM 删除。
xxd 16进制转换
Linux 中如果要查看文件的 16 进制表示,可以通过 xxd 工具,在 Vim 中使用 :%!xxd
即全部内容执行外部命令 xxd
,
反向转换为 :%!xxd -r
。