编码问题

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编码方案编码的字节流

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

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