编码问题
Abstract Charater Repertoire
抽象字符集:ACR
字符
字母、数字、标点、表意文字(汉字)、符号或其他文本形式的 “原子”
抽象字符
抽象的字符,包括空白、不可打印的字符
对于某些语言中,抽象字符应该还包括发音字符
如:印度语中单词“नमस्ते”
- 有6个字符[‘न’, ‘म’, ‘स’, ‘्’, ‘त’, ‘े’],
- 其中4、6两个字符在单词不出现,是发音字符
Abstract Charater Repertoire
抽象字符集:ACR,抽象字符的集合
- 集合表明无序性
- 有时也简称为字符集(charater set)
- 有开放(字符不会改变)和封闭之分(会扩张)
Coded Character Set
编码字符集:CCS
Code Point
码位:抽象字符集中与字符关联的数字编号
- 一般是非负整数
- 习惯上有16进制表示
Coded Character Set
编码字符集:CCS,每个所属字符都分配了码位的抽象字符集
- 经常简称为字符集(charater set),同ACR弄混
- 字符与码位一一映射
- 可以更加方便的引用字符集中的某个元素
- 可以类比于dict
字符集(抽象、编码)举例
US-ASCII
ACSII字符集
128个抽象字符,封闭字符集
主要包括
- 控制字符:回车、退格、换行
- 可显示字符:英文大小写、阿拉伯数字、西文符号
- 一般字符集都是兼容ascii编码字符集,即相同字符的码位相同
ISO-8859-X
扩展的ASCII字符集
- 涵盖了大多数西欧语言字符、希腊语
GBXXXX
国标系列字符集:每个标准包含的字符数量不同、对应的编码方案 也不不完全相同
GB2312:信息交换用汉字编码字符集基本集
- 包含汉字覆盖99.75%的使用频率
- 人名、古汉语等罕用字不能处理
GBK:汉字内码扩展规范
- 包括21003个汉字
- 支持国际标准ISO/IEC10646-1和国家标准GB13000-1中 全部中日韩汉字
- 包含了BIG5编码中的所有汉字
- 兼容GB2312
GB18030:信息技术中文编码字符集
- 其中收入汉字70000余个
- 以汉字为主并包含多种我国少数民族文字(如藏、蒙古、 傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集 强制性标准
- 兼容GBK
Big5
Big5字符集:主要包含台湾、香港地区繁体字
Universal Character Set/Unicode/UCS
统一字符集/Unicode字符集:ISO/IEC 10646定义的编码字符
开放字符集,码位无上限,容纳一切字符,包括emoji等
UCS中码位不是连续分配的
目前为止,分为
0x0000~0x10FFFF
共17个平面其中0平面
0x0000~0xFFFF
称为 basic multilingual planeBMP
中码位只有16bit长度,能够节约大量存储空间,有 战略意义因此“常用”语言的常用字符放在
BMP
,其他不常用的字符 只能放在其他平面
unicode本身是指一系列用于计算机表示所有语言字符的标准
Character Encoding Form
字符编码表:CEF,将码位映射为码元序列
fixed-length-encoding:定长编码,对每个码位(字符) 赋予长度同为m的码元(位串)
- 封闭字符集符号有限,可以直接确定一一对应编码表
variable-length-encoding:变长编码,允许对不同码位 赋予不同长度的码元
- 开放字符集包括符号无上限,无法定长码元表示码位, 必须有某种方式将码位一一映射为码元序列
- 封闭字符集出于节约成本考量,也可能使用变长编码 如:Huffman编码
- 码元:能用于处理或交换编码文本的最小比特组合(位串)
Unicode定义的CEF
本质思想:预留标记位值使码元序列的长度实现变长
UTF-8
- 码元为1B
- 对BMP中字符一般需要1~3B,BMP外需要4B
- 兼容ASCII编码表(方案)
- 不同于编码字符集兼容的意义,基本上编码字符集都 兼容ASCII编码字符集,即对应字符码位相同
- 兼容编码表指,“ASCII编码方案”可以使用UTF-8解码 方案直接解码
UTF-16
- 码元为2B
- 对BMP中字符一般需要2B,BMP外需要4B
UTF-32
- 码元为4B
Prefix-Free Code
(自由)前缀码:所有代码码元都不是另一个字符码元的前缀
可以简单扫描位串直到得到一组有效码元,转换为相应字符
这样编码表可以很容易用一棵(编码)二叉树表示
- 树左向边标记为0、右向边标记为1
- 叶子节点表示字符,根节点到叶子节点路径为其码元
- 树中叶子节点到其他叶子节点的简单路径不存在,即码元 不可能为其他码元前缀
- 所以任何二叉树对应一套编码表
这种编码方案一般用于产生平均长度最短的位串
- 因此这类编码方案以bit为单位,而不是以byte为单位
Huffman Encoding
哈夫曼编码:prefix-free code的一种
根据字符出现频率进行编码
- 需要事先知道字符出现概率
- 可以事先扫描给定文本,对文本中字符出现字符计数
- 将较短位串分配给高频字符、较长位串分配给低频字符
若字符独立出现,则哈夫曼编码是最优编码(最短长度编码)
需要把编码树信息包含在编码文本中才能正确解码
- 构造哈夫曼树的贪婪算法参见组合问题
Adaptive Huffman Encoding
利用已经处理字符串动态更新编码
Lempel-ziv
对字符串编码
Character Encoding Schema
字符编码方案:CES,字符编码表+字节序列化方案,将码位 映射为字节流
- 大小端序问题:码元高位还是低位字节在前
- 字节序标记问题:不同程序之间端序交流
- 通常所说编码、解码就是指使用CES
应用场合
CES是真正的应用层面,需要给出具体存储方案实现, 前述都是理论上protocol
所有字符串存储介质中,磁盘、内存都采用某种具体CES 实现存储
Java、Python3这样的偏上层语言,字符串对象在内存中 通常采用UTF-16
C这样偏底层语言,基本上按照源文件的编码确定,即将 源文件中对应字符串对应字节,但现在C/C++中还有一种 宽字符w_char类型
- 以上仅对Unicode而言,对于ASCII来说没有区分必要
内存CSE说明
内存中如果不使用某种CES实现,直接使用码元,一样会出现 长度问题,所以显然会使用某种CES方案
虽然在内存中,字符仍然使用某种编码方案得到字节流存储,但 这个字节流并不是这个字符,码位才“是”这个字符
大部分提供Unicode处理语言会自动处理字符,不仅仅 是字节
在考虑字符串问题时,可以“抽象的”忽略具体存储方式, 认为存储的就是“码位”本身
Byte Order Mark
字节序标:BOM,放置于编码字节开始处的特殊字节序列,表明 序列大小端序
0xFFFE
:小端序,低位在前0xFEFF
:大端序,高位在前
Unicode族CES方案
UTF
unicode transfromation format:历史上是指CES,而UTF-X 现在可以同时指代CES和CEF,Unicode族标准CEF方案
UTF-8:utf-8编码表码元为1B,不存在字节序问题
- 指代CES和CEF没有什么区别,CEF只有一种
UTF-16:指代CES和CEF时有歧义,需要明确指明是 UTF-16 encoding form(码元序列)、 UTF-16 encoding schema(字节流)
- UTF-16-le:utf-16编码表小端版本
- UTF-16-be:yutf-16编码表大端版本
- UTF-16:utf-16编码表带BOM版本,大小端均可
- UTF-16 CES表示BMP(包含大部分常用字符)只需要2B, 权衡了内存占用、处理便捷,适合作为内存中字符串的 编码方案
UTF-32
- UTF-32le:
- UTF-32be
- UTF-32
UCS
Unicode还有两种非标准CES
- UCS-2:使用2B定长序列化码位
- 可以视为UTF-16的子集
- 不支持BMP外的字符表示
- UCS-4:使用4B定长序列化码位
- 可以视为UTF-32的子集
其他字符集CES方案
US-ASCII、GBK都有自己的编码方案,只是编码方案太简单,以至于 CCS、CEF、CES三层合一
- ASCII编码方案:1B的低7位表示一个字符
- ISO-8895-1编码方案:1B表示一个字符
- GB2312编码方案:2B表示一个汉字
- 第一个字节:区字节,高位字节
- 第二个字节:位字节,低位字节
- GBK编码方案:2B表示一个汉字
- 兼容GB2312方案
- 编码范围:
0x8140~0xFEFE
,剔除0xxx7F
- GB18030编码方案:变长字节1B、2B、4B
- 兼容GBK方案
- Big5编码方案:2B表示一个汉字
- 字节顺序类似GB2312
ANSI编码
各个国家、地区独立制定、兼容ASCII编码,但彼此之间不兼容 的编码方案,微软统称为ANSI编码
ANSI编码一般代表系统(仅win)默认编码方式,在不同系统中 指不同的编码方案
- 英文操作系统:ISO-8859-1
- 简体中文:GBxxxx编码
- 繁体中文:Big5编码
- 日文:Shift JIS编码
- 默认ANSI编码可以通过设置系统
Locale
更改
- win下系统
Locale
决定代码页,用户Locale
决定数字、 数字、货币、时间与日期格式
Transfer Encoding Syntax
传输编码语法:TES,有时候需要对字节流再次编码以进行传输
- 如:某些字符不允许出现在传输流中
举例
- base64编码:将字节流映射成64个安全字符集组成的字符流
输入、输出辨析
输入
所有的输入都是经过CES编码的字节流(包括数字)
- 文件输入流:文件编码方案决定
- 标准输入流(terminal):terminal编码方案决定
- 管道传输流:由管道输入端的编码方案决定
处理
这里应该有两种处理方式
将输入视为字节流,不做任何处理,直接按字节存储在 内存中
- 将输入字节流视为其自身编码方案字节流,直接储存
将输入视为字符串,尝试解码
若解码发现无法解释位串
- strict:报错
- replace:将违规字符替换为有效字符
- 替换为某种
?
:很多应用采用此方式,是乱码 发生的主要原因 - 有些也替换为Unicode码位
- 替换为某种
- ignore:忽略该位串
而解码后字符串的在内存中的存储,取决于解释器、编译器 、系统等处理主体的编码方案
- 以上只是对真正有字符串类型的语言Python、Java有这样区分
- 对于没有字符串类型的语言C并没有真正意义上的字符串,只有 字节串,不涉及解码、自身字符串内存存储的问题,仅有换行符 转义问题(若处理换行符被视为解码)
输出
所有输出都是编码后字节流(包括数字)
- 文件输出流:
write
- 标准输出流(terminal):
print
- 管道传输流
- 需要注意的是,输出的字节流编码方案和处理主体在内存中编码 方案不一定相同,和编程实现、平台等因素都有关,比如很多 默认输出编码方案为utf-8
- 同样的,此输出流是对于其接收者而言仅仅是字节流,需要自行 决定如何处理字节流
- 此输出是指传递给直接输出外部的输出,有些语言在输出前 会隐式调用函数生成字符串
其他常见问题
乱码
乱码主要有以下3种可能
- 读取乱码:真正的乱码
- 读取时没有正确解码,内容被替换,打印输出
- 存储乱码:保存时已经是“乱码”,其实也不能算是乱码
- 读取时没有正确解码,内存中内容已经被错误,保存后内容 保持错误
- 内存中数据正确,但保存使用的编码方案不支持某些字符, 内容被替换
- 缺少字体库
- 乱码不是其实已经是将不能打印的字符剔除、替换,能看到的 乱码已经是程序认为“正确解码的字符串”
换行符处理
鉴于以下两种行为
- win下换行需要两个字符
\r\n
标识,linux下只需要\n
即可 - 编程语言往往类似Linux只需要
\n
即标识换行
- Vim中在内存中以
<CR>
标识换行
- win下换行需要两个字符
在win下很多语言以字符串模式:读取字节流时会自动将
\r\n
替换为\n
、写出字节流时替换回\r\n
- Python这种原生支持字符串语言,这个特性可以看作字符串 解码的行为
- C这种原生只支持字节串的语言,这个特性可能是二进制、 字符串读写的唯一区别
例子
以UTF-8编码方案为例
输入的所有的内容都是由UTF-8编码方案编码的字节流
处理主体获取字节序列,根据指令处理字节序列
- 比如字节序列编码和处理主体编码不同,将其解码为 主体编码方案
- 比如按照约定将字节序列转变为不同类型的数据
输出则是将需要输出的内容(包括数字等)转换字节流传给底层 函数