# 字节编码

  • 二进制编码(Binary Encoding):指将一个字符转换成二进制编码值,即从 string 类型转换成 bytes 类型。
    • 二进制编码值通常以字节为单位存储,比如长度为 1 字节、2 字节,因此又称为字节编码。
    • 例如字母 'A' 按 ASCII 编码之后,会转换成二进制值 0100 0001

# 相关概念

  • 字节:在计算机中,指一个 8 bits 大小的二进制数据。
  • 字符:在计算机中,指一个自然语言的文字,比如字母 'A' ,通常用 char 数据类型的变量存储,比如一个字母 'A' 通常占 1 字节的存储空间。
  • 字符串:在计算机中,指一连串的多个字符,通常用 string 数据类型的变量存储。
  • 文本:在现实世界中,指由一些自然语言的文字组成的信息,比如一句话。这些信息传入计算机中时,视作一条字符串,通常用 string 数据类型的变量存储。
  • 计算机程序将文本写入文件中存储时,通常要转换成二进制编码的数据。例如:
    1. 程序将一个字符串 "Hello World!" 按 ASCII 格式编码,转换成二进制数据,写入文件。
    2. 程序从文件读取二进制数据,按 ASCII 格式解码,转换成一个字符串 "Hello World!" 。
      • 如果解码的格式与编码格式不一致,得到的字符串就可能与原字符串不一致,比如出现乱码。
  • 如果一个字符的编码值超过一个字节,则称为宽字符。

# 字节序

  • 如果一个数据类型的长度超过 1 个字节,则需要考虑字节序(Byte Order)的问题:
    • 大端序(Big-Endian):高位字节先存储。例如 0x11223344 存储为 0x11 0x22 0x33 0x44 ,最高位的字节 0x11 存储在地址的最低位。
    • 小端序(Little-Endian) :低位字节先存储。例如 0x11223344 存储为 0x44 0x33 0x22 0x11 ,最高位的字节 0x11 存储在地址的最高位。
  • 如何判断一段文本的字节序?
    • 可以为每种字节序定义一种编码格式。例如将 UTF-16 编码格式细分为 UTF-16be 和 UTF-16le 。
    • 可以在一个文本文件的开头加上几个字节作为标记,用于声明字节序,称为 BOM(Byte Order Mark)。

# 单字节编码

# ASCII

  • :美国信息交换标准代码(American Standard Code for Information Interchange),一种简单、基础的单字节编码格式。
  • 编码范围为 0x00 ~ 0x7F ,总共定义了 128 个字符。
  • 收录了拉丁字母、一些控制字符。

# ISO-8859-1

  • :又称为 Latin-1 ,是一种单字节编码格式,编码范围为 0x00 ~ 0xFF 。
  • 是 ASCII 码的超集。
  • 收录了拉丁字母、希腊语、阿拉伯语等字符。
  • 用到了单字节的所有编码。
    • 因此,任意一个 bytes 对象,不管它存储了什么二进制内容,即使存储了一个图片文件,都可以按照 ISO-8859-1 格式解码成 str 对象,虽然得到的 str 字符串不一定有意义:
      >>> b'\xFF'.decode('ISO-8859-1')
      'ÿ'
      >>> '你'.encode('gbk').decode('ISO-8859-1')
      'Äã'
      

# Windows 1252

  • :又称为 cp1252 、ANSI 字符集,是 ISO-8859-1 的超集。

# 多字节编码

# Unicode

  • :一种字符集,又称为万国码、国际码、统一码,收录了世界上很多种语言的字符,给每个字符分配了唯一编码。
  • 编码范围为 U+000000 ~ U+10FFFF ,总共大约有 1.1 百万个码位(Code Point),可用于映射字符。
    • 开头两个字节从 U+00 到 U+10 划分为 17 个平面(plane),每个平面包含 2^16=65536 个码位。
    • 第一个平面称为第零平面、基本多语言平面(Basic Multilingual Plane, BMP),其它平面称为辅助平面。
    • 第零平面包含了最常用的字符,其中 D800 ~ DFFF 范围内的码位保留,不映射 Unicode 字符,因此通常被 UTF 编码使用。
  • 每个字符编码为 6 个十六进制数,加上 U+\u 作为前缀。
    • 只考虑第零平面时,可以只使用 4 个十六进制数,比如字母 A 表示成 U+0041 。

# UTF

  • :Unicode 转换格式(Unicode Transformation Format)
  • Unicode 字符的编码较长,大部分码位都用不到,浪费存储空间。因此实际使用时,通常通过 UTF 转换成更短的编码格式。

# UTF-8

  • :一种编码格式,兼容 ASCII 码。
  • 字符的编码长度可变,为 1 ~ 4 个字节。比如存储 ASCII 码字符时只用 1 个字节,存储中文字符时使用 3 个字节。
  • UTF-8 的编码长度可变,因此不存在多种字节序。
    • 按照 UTF-8 规范,处理 UTF-8 编码的文本时,不需要考虑字节序。
    • 不过,Microsoft 公司的 Office 等软件习惯了在处理 UTF-8 编码的文本时,加上 0xEF 0xBB 0xBF 三个字节作为 BOM 标记,因此与标准的 UTF-8 格式不同,这会导致一些问题:
      • 例如,用 VS Code 软件编写一个 UTF-8 编码、包含中文字符的 csv 表格文件之后,用 Excel 软件打开该文件,显示的字符会乱码。此时可用 Notepad++ 软件将该文件转换成 UTF-8-BOM 编码格式。或者用 iconv 1.csv -f utf-8 -t gbk > 2.csv 命令将该文件转换成 gbk 编码格式,则不需要声明字节序。
      • 反之,用 Excel 导出一个包含中文字符的 csv 表格文件之后,用 VS Code 打开该文件,显示的字符会乱码。此时可用 Notepad++ 软件将该文件转换成 UTF-8 编码格式。

# UTF-16

  • :一种编码格式,不兼容 ASCII 码。
  • 大部分字符的编码长度是 2 字节,少部分不常用的字符则是 4 字节。
  • 根据字节序的不同,细分为两种编码格式:
    • UTF-16be :大端序,BOM 标记为 0xFE 0xFF
    • UTF-16le :小端序,BOM 标记为 0xFF 0xFE
  • 例:
    >>> '12'.encode('utf-16')   # 编码格式名称中没有声明字节序,Python 默认会按小端序处理
    b'\xff\xfe1\x002\x00'       # 编码之后,会在开头加上 BOM 标记,末尾加上空字符 \x00 表示结束
    >>> 'A'.encode('utf-16')
    b'\xff\xfeA\x00'
    >>> ''.encode('utf-16be')   # 如果编码格式名称中声明了字节序,则不必再加上 BOM 标记
    b''
    >>> 'A'.encode('utf-16le')
    b'A\x00'
    

# UTF-32

  • :一种编码格式,不兼容 ASCII 码。
  • 每个字符的编码长度都是 4 字节。
  • 根据字节序的不同,细分为两种编码格式:
    • UTF-32be :大端序,BOM 标记为 0x00 0x00 0xFE 0xFF
    • UTF-32le :小端序,BOM 标记为 0xFF 0xFE 0x00 0x00

# UCS

  • UCS

    • :通用编码字符集(Universal Coded Character Set),由 ISO 10646 标准定义,相当于 Unicode 字符集的子集。
  • UCS-2

    • :一种编码格式,是 UTF-16 的子集。
    • 只对 Unicode 第零平面的字符进行编码。
    • 每个字符的编码长度都是 2 字节。
    • 根据字节序的不同,细分为两种编码格式:
      • UCS-2be
      • UCS-2le
  • UCS-4

    • :一种编码格式,是 UTF-32 的超集。
    • 每个字符的编码长度都是 4 字节。

# 中文编码

# GB2312

  • :一种简体中文的编码格式,于 1980 年发布,已经过时。
  • 在 Windows 中别名为 CP936 。
  • 收录了 6k 多个汉字、拉丁字母等。

# BIG5

  • :一种繁体中文的编码格式,又称为大五码,于 1983 年发布。
  • 收录了 1w 多汉字、拉丁字母等。

# GBK

  • :一种中文编码格式,于 1995 年发布。
  • 向下兼容 GB2312 ,向上支持 ISO 10646 标准,也收录了 BIG5 中的繁体汉字。总共收录了 2w 多个汉字。
  • 英文字符的编码长度为 1 字节,中文字符的编码长度为 2 字节。

# GB18030

  • :一种中文编码格式,于 2000 年发布。
  • 是 GBK 的超集。总共收录了 7w 多个汉字。
  • 字符的编码长度可变,包括:
    • 1 字节,向下兼容 ASCII 。
    • 2 字节,向下兼容 GBK 。
    • 4 字节

# import codecs

:Python 的标准库,用于管理 Python 的编解码器。

  • 它将编解码器称为 codec ,包括 encoder 和 decoder 。
  • 它为所有编解码器定义了抽象的基类:
    class Codec:
        def encode(self, input, errors='strict'):
            pass
    
        def decode(self, input, errors='strict'):
            pass
    
  • Python 解释器内置了很多种编码格式的编解码器,它们都在 codecs 模块中注册了。
    • 编码格式的名称不区分大小写。对于连字符 - ,可以替换成下划线 _ ,也可以省略。
    • 例:查询一种编码格式对应的编解码器
      >>> import codecs
      >>> codecs.lookup('utf-8')
      <codecs.CodecInfo object for encoding utf-8 at 0x151dc6a99a0>
      >>> codecs.lookup('utf_8')
      <codecs.CodecInfo object for encoding utf-8 at 0x151dcf59400>
      >>> codecs.lookup('utf8')
      <codecs.CodecInfo object for encoding utf-8 at 0x151dcf59280>
      

# import chardet

:Python 的第三方库,用于检测 bytes 对象采用的字节编码格式。

  • 安装:pip install chardet
  • 其原理是通过比对各种字符集的特征字符来猜测编码格式。
    • 检测的数据量太少就容易猜错。
    • 不能处理 str 对象,因为 str 已经是按 Unicode 格式编码了。
  • 例:检测一个字符串
    >>> import chardet
    >>> chardet.detect(b'Hello, world!')
    {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}    # confidence 表示可信度,这里为 100%
    >>> chardet.detect('你'.encode('GBK')))
    {'encoding': None, 'confidence': 0.0, 'language': None}
    >>> chardet.detect('你好'.encode('GBK'))
    {'encoding': 'TIS-620', 'confidence': 0.3598212120361634, 'language': 'Thai'}
    >>> chardet.detect('你好,欢迎来到这里!'.encode('GBK'))      # 增加检测的字符数量时,检测结果可能改变
    {'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'}
    
    • 即使 chardet 将一个字符串识别为 GB2312 格式,建议也当作 GBK 格式解码。以免 chardet 将 GBK 格式的字符串,误识别成了 GB2312 格式。
  • 例:检测一个文件
    >>> f = open(path, 'rb')
    >>> chardet.detect(f.read())['encoding']
    'GB2312'