编码问题

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 plane

    • BMP中码位只有16bit长度,能够节约大量存储空间,有 战略意义

    • 因此“常用”语言的常用字符放在BMP,其他不常用的字符 只能放在其他平面

  • unicode本身是指一系列用于计算机表示所有语言字符的标准

Character Encoding Form

字符编码表:CEF,将码位映射为码元序列

  • fixed-length-encoding:定长编码,对每个码位(字符) 赋予长度同为m的码元(位串)

    • 封闭字符集符号有限,可以直接确定一一对应编码表
  • variable-length-encoding:变长编码,允许对不同码位 赋予不同长度的码元

    • 开放字符集包括符号无上限,无法定长码元表示码位, 必须有某种方式将码位一一映射为码元序列
    • 封闭字符集出于节约成本考量,也可能使用变长编码 如:Huffman编码
  • 码元:能用于处理或交换编码文本的最小比特组合(位串)

Unicode定义的CEF

本质思想:预留标记位值使码元序列的长度实现变长

UTF-8

  • 码元为1B
  • BMP中字符一般需要1~3BBMP外需要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下很多语言以字符串模式:读取字节流时会自动将\r\n 替换为\n、写出字节流时替换回\r\n

    • Python这种原生支持字符串语言,这个特性可以看作字符串 解码的行为
    • C这种原生只支持字节串的语言,这个特性可能是二进制、 字符串读写的唯一区别

例子

以UTF-8编码方案为例

  • 输入的所有的内容都是由UTF-8编码方案编码的字节流

  • 处理主体获取字节序列,根据指令处理字节序列

    • 比如字节序列编码和处理主体编码不同,将其解码主体编码方案
    • 比如按照约定将字节序列转变为不同类型的数据
  • 输出则是将需要输出的内容(包括数字等)转换字节流传给底层 函数

转义序列

C0C1 控制字符集

C0 控制字符集

  • CO 控制字符集码位范围:0x00 - 0x1F

    • ASCII 中定义控制字符标准
    • 码位限定在 1byte 可以表示,避免终端机需要实现状态机处理多字节控制序列
    • 现只有少数控制字符被使用
  • C0 控制字符码位范围之外,还有定义有两个具备控制符特点的字符

    • 0x7Fdelete
    • 0x20space

C1 控制字符集

  • C1 控制字符集码位范围:0x80 - 0x9F

    • 8bits ISO/IEC 8859 ASCII 扩展提出后
      • 考虑到可打印字符的最高比特位去掉之后不应变成控制字符
      • C0 控制字符集作为低位、最高位置 1,得到 C1 控制字符集
    • C1 码位在经常被私有编码方案(Windows-1252Mac Os Roman)用于提供额外的可打印字符
  • ISO/IEC 8859 ASCII 扩展标准中指定

    • 为兼容 7bits 传输,所有 C1 控制字符使用 ESC 开头的 7bits 字符序列表示

标准 C 转义规则

  • 非打印(包括控制)字符可以通过其 ASCII 码位 16 进制、8 进制表示
    • \0[ooo]:八进制数 oo 码位字符
    • \x[hh]:十六进制数 hh 码位字符
      • \x0a:同 \n
  • 针对常用非打印字符,有如下简写方式
    • \\:反斜杠 \
    • \':单引号 '
    • \":双引号 "
    • \aBEL ASCII 响铃
    • \bBS ASCII退格
    • \fFF ASCII 进纸
    • \nLF/NL ASCII 换行,开启新行
    • \rCR ASCII 回车,“指针移至行首”
    • \tTAB ASCII 制表符
    • \vVT 垂直制表符

ANSI Escape Sequences

ANSI:一种 In-band Signaling 的转义序列标准,用于控制终端上 光标位置、颜色、其他选项

  • 在文本中嵌入的 ANSI 转义序列,终端会将 ANSI 转义序列解释为相应指令,而不是普通字符

    • ANSI 转义序列使用 ASCII 中字符传递所有信息
  • ANSI 转义序列有不同长度,但都

    • ASCII 字符 ESC0x1b) 开头
      • 8 进制表示:\033
      • 16 进制表示:\x1b
    • 第二字节则是 0x45 - 0x5FASCIIi @A-Z[\]^_)范围内的字符
  • 标准规定,在 8bits 环境中

    • ANSI 转义序列前两个字节的序列可以合并为 0x80 - 0x9F 范围内的单个字节(即 C1 控制字符)
    • 但在现代设备上,C1 控制字符码位被用于其他目的,一般不被使用
      • UTF-8 编码对 x80 字符本就需要 2bytes
      • Windows-1252 编码将 C1 控制字符码位挪作他用

No-CSI - 非控制序列

序列(省略 ESC 对应 C1 名称 效果
N 0x8E SS2 - Single Shift 2 从替代 G2 字符集中选择字符
O 0x8F SS3 - Single Shift 3 从替代 G3 字符集中选择字符
P 0x90 DCS - Device Control String 控制设备
D 仅换行,不重置光标至行首
E 换行并充值光标至行首,类似LF
H 制表,类似TAB
M 翻转换行,回到上一行
X 0x98 SOS - Start of String 引用由 ST 终止的一串文本参数
^ 0x9E PM - Privacy Message 引用由 ST 终止的以穿文本参数
_ 0x9F APC - Application Program Command 引用由 ST 终止的一串文本参数
c - RIS - Reset to Initial State 类似clear命令
[ 0x9B CSI - Control String Sequence 控制序列导入器,某些终端中也可以使用0x9D
\ 0x9C ST - String Terminator 终止其他控件得字符串
] 0x9D OCS - Operating System Command 启动操作系统使用的控制字符串
%G 选择 UTF8 作为字符集
#8 DEC 屏幕校准测试,使用E填充整个终端

Control Sequence Introducer

控制序列导入器:ESC[ + 若干参数字节 + 若干中间字节 + 一个最终字节

  • 常见序列只是把参数用作一系列分号分隔的数字,如:1;2;3

    • 缺少的数字视为 0
    • 某些序列(CUU)把 0 视为 1,以使缺少参数情况下有意义
  • 一部分字符定义“私有”,方便终端制造商插入私有序列

    • 参数字节 <=>? 的使用:ESC[?25hESC[?251 打开、关闭光标显示
    • 最终字节 0x70 - 0x7F
组成部分 字符范围 ASCII字符
参数字节 0x30~0x3F 0-9:;<=>?
中间字节 0x20~0x2F 、!"#$%&'()*+,-./
最终字节 0x40~0x7E @A-Z[]^_a-z{}~, `

光标移动

序列内容 名称 效果
[n]A/[n]B/[n]C/[n]D CU[UDFB] - Cursor Up/Down/Forward/Back 光标移动[n]格,在屏幕边缘则无效
[n]E/[n]F Cursor Next Line/Previous Line 光标移动至下[n]行/上[n]行开头
[n]G Cursor Horizontal Absolute 光标移动到第[n]
[n;m]H CUP - Cursor Position 光标绝对位置
[n;m]f Horizontal Vertical Position CUP
[n]J Erase in Display 清除屏幕部分区域:0 - 光标至末尾;1 - 开头至光标;2 - 整个屏幕
[n]K Erase in Line 清除行内部分区域
[n]S Scroll Up 整页向上滚动 [n]
[n]T Scroll Down 整页向下滚动 [n]
s Save Cursor Position 保存光标当前位置
u Restore Cursor Position 恢复光标位置

窗口

序列内容 名称 效果
5i - 打开辅助端口,通常用于本地串行打印机
4i - 关闭辅助端口,通常用于本地串行打印机
6n Device Status Report ESC[n;m]R 报告光标位置

Select Graphic Rendition

  • SGR 选择图形再现:ESC[[n]m
    • [n]:多个参数用 ; 分隔,缺省为 0
    • m:结束字节
样式
设置值 显示效果 取消值
0 所有属性值重置为默认值,用于取消对后续输出影响
1 高亮或粗体 22
2 半亮 22
4 下划线 24
5 闪烁 25
7 反显,前景、背景色交换 27
8 隐藏,前景、背景色相同,可能不支持 28
9 删除线 29
53 上划线 55
11-19 选择替代字体
3/4位色
前景色值 背景色值 颜色 高亮前景色值 高亮背景色值
30 40 黑色 90 100
31 41 红色 91 101
32 42 绿色 92 102
33 43 黄色 93 103
34 44 蓝色 94 104
35 45 紫红色 95 105
36 46 青蓝色 96 106
37 47 白色 97 107
38 48 控制使用256位、RGB色
39 49 默认颜色

ansi_sgr_colors_16

  • 可通过反显 7 实现背景色、高亮 1 实现多高亮色
8bits 色
  • 8bits 色设置格式
    • ESC[38;5;37m:设置256位前景色
    • ESC[48;5;37m:设置256位背景色
  • 预定义 8bits 色情况
    • 0-7:标准颜色,同 ESC[30-37m
    • 8-15:高强度颜色,同 ESC[90-97m
    • 16-23116 + 36*r + 6*g + b($0 leq r,g,b leq 5$ 得到 6 6 6 立方)
    • 232-255:24阶灰阶

ansi_sgr_colors_256

24bits 色
  • 24bits 色设置格式

    • ESC[38;2;<r>;<g>;<b>m:选择 RGB 前景色
    • ESC[48;2;<r>;<g>;<b>m:选择 RGB 辈景色
  • 字符内容体系结构有一个不太受支持的替代版本

    • ESC[38:2:<Color-Space-ID>:<r>:<g>:<b>:<unused>:<CS tolerance>:<Color-Space: 0="CIELUV";1="CIELAB">m:选择 RGB 前景色
    • ESC[48:2:<Color-Space-ID>:<r>:<g>:<b>:<unused>:<CS tolerance>:<Color-Space: 0="CIELUV";1="CIELAB">m:选择 RGB 背景色
  • 支持 libvte 的终端上支持 ISO-8613-3 的 24bits 前景色、背景色设置,如 XtermKonsole
  • 24bits 色的替代版本是 ISO/IEC 8613-6 采用的 ITUT.416 信息技术

字体杂记

字体文件

  • TTFTruetype Font

    • 苹果公司创建,MacWin 上最常用的字体文件格式
    • 由数学表达式定义、基于轮廓的字体文件
    • 保证了屏幕、打印输出的一致性,同时也可以和向量字体一样随意缩放
  • TTCTrueType Collection

    • 微软开发的新一代字体文件,多个 TTF 文件合并而成
    • 可以共用字体笔画文件,有效的节约空间
    • 兼容性不如 TTF,有些应用无法识别
  • FON

    • Win 上最小的字体文件格式
    • 固定大小的位图格式,难以调整字体大小

字体分类

西文字体分类

  • 西文字体分类方法有很多种,但是太学术,不常用,常用分类的可以看计算机字体族
  • Thibaudeau 分类法
    • 法国字体排印师 Francis Thibaudeau 于 1921 年提出
  • Vox-ATypl 分类法
    • Maximilien Vox 于1954年提出,是比较早、基础、业内有过影响力的分类法
  • Fontshop 自家的分类法
    • 在已有的思路的基础上,基于字体开发的独特的分类法
    • 适合网上搜索字体,网罗了超过 15 万字体
  • Linotype 提供的3种分类法
    • by category + by usage+ by theme
    • 后 2 者是面向一般字体用户,重视字体的用途来合理分类

中文字体分类

  • 中文字体则没有一个明确的分类体系,仅能大概分类
  • 宋体(明体):最能代表汉字风格的印刷字头
  • 仿宋:相当于雕版时代的魏碑体
  • 楷体:标准化的楷书,毛体书法的产物
  • 黑体:汉字在西方现代印刷浪潮冲击下的产物
  • 圆体:海外地区率先开发、使用

计算机字体分类

  • serif:有衬线字体

    • 特点
      • 笔画有修饰,末端向外展开、尖细或者有实际衬线
      • 文字末端在小号字体下容易辨认,大号可能模糊或有锯齿
      • Times New RomanMS GeorgiaDejaVu Serif
      • 宋体仿宋
    • 两种衍生字体
      • petit-serif:小衬字体,可以当作无衬线
      • slab-serif:雕版衬线,末端变化非常明显
  • san-serif:无衬线字体

    • 特点
      • 末端笔画清晰,带有一点或没有向外展开、交错笔画
      • serif相比,字体较小时可能难以分辨、串行(阅读)
    • 举例
      • MS TrebuchetMS ArialMS Verdana
      • 黑体圆体隶书楷体
  • monospace:等宽字体

    • 特点
      • 每个字形等宽,因此常作为终端所用字体
    • 举例
      • CourierMS Courier NewPrestige
      • 多数中文字体(中文字体基本都等宽)
  • cursive:手写字体

    • 举例
      • Caflisch ScriptAdobe Poetica
      • xx手写体、xx行草
  • fantasy:梦幻字体(艺术字)

    • 举例
      • WingDingsWingDings2Symbol
      • 各种奇怪名字的字体
  • serifsan-serif 是西文字体的两大分类
  • 而后应该是计算机的出现带来的monospace的兴起
  • 最后面两种在正式场合中不常用