字符编码简述

##基本概念

计算机存储的是二进制数据, 0/1 组成的比特, 我们在屏幕上看到的各种显示字符, 包括字母, 汉字, 数字, 各种符号等等, 都是由一串二进制比特转码之后得到的. 这种转码的规则就叫做字符编码. 例如最常见的 ASCII 规则: 字母'A': 对应二进制 0x41, 101000001.

##ASCII

全称是American Standard Code for Information Interchange, 美国信息交换标准代码. 这是美国人设计的标准, 只支持基础拉丁字符. ASCII 的设计很简单, 用一个字节来表示一个字符, 最高位的取值永远为'0', 字符范围 0x00-0x7F, 一共包含了 128 个字符, man ascii 可以在 Posix 中看到所有的 ASCII 字符集.

##EASCII

Extended ASCII, 延伸美国标准信息交换码, 相比于 ASCII, EASCII 最高位不再限制为'0', 增加了 0x80-0xFF 的这一段. EASCII 扩充出来的符号包括表格符号, 计算符号, 希腊字母和特殊的拉丁符号, 基本解决了整个西欧的字符编码问题.

##GB2312

国家标准简体中文字符集, 并在1981年5月开始在国内实行. GB2312 基于分区处理, 编码时采用2字节, 兼容 ASCII 编码, 高字节0xA1-0xF7, 低字节0xA1-0xFE, 高位都是'1', 所以能兼容 ASCII 编码. GB2312 字符集包含了 7445 个字符, 其中 6763 个汉字, 已经能满足基本需求.

##GBK

GB2312 支持的汉字毕竟有限, 部分生僻汉字, 包括繁体字, 以及日语汉语等, 并未有收录在内. 于是微软利用 GB2312 未使用的编码空间, 收录 GB13000 中的全部字符制定了 GBK 编码, 最早实现于 Windows 95 简体中文版.

虽然 GBK 收录 GB13000 的全部字符, 但编码方式并不相同. GBK 编码最多使用 2 个字节: 字节为0x00-0x7F兼容 ASCII, 最高位为 1 的字节表示 2 字节编码, 高字节范围为0x81-0xFE, 低字节范围为0x80-0xFE. GBK 字符集一共支持 21886 个符号.

##GB18030

GB18030, 全称: "国家标准 GB18030-2005《信息技术 中文编码字符集》", 是中国最新的内码字集, 是 "GB18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》"的修订版. 与 GB2312 完全兼容, 与 GBK 基本兼容, 支持 GB13000 及 Unicode 的全部统一汉字, 共收录汉字70244个. GB18030 采用多字节编码, 每个字符由1或2或4个字节进行编码.

##BIG5

Big5, 又称为大五码或五大码, 共收录 13060 个汉字, 开始支持繁体中文. Big5 普及于台湾, 香港与澳门等繁体中文通行区, 但长期以来并非当地的国家标准, 而只是业界标准. Big5 是一套双字节字符集, 使用了双八码存储方法, 以两个字节来安放一个字: 高字节范围0x81-0xFE, 低字节范围0×40-0x7E, 及0xA1-0xFE.

##Unicode

因为不同的语言采用不同的编码, 有可能导致冲突与不兼容性, 从而出现乱码现象. 为了解决这些局限, 国际标准化组织(ISO)发布了统一编码标准 Unicode, 采用4个字节表示一个字符, 这样理论上 Unicode 能表示的字符数就达到了 2^31 约 21 亿左右字符, 完全可以涵盖世界上一切语言所用的符号.

Unicode 是一种编码标准, 它的实现有很多种, 包括 UTF(Unicode Translation Format) 的 UTF8 和 UTF16, 上文提到的 GBK 和 GB18030 也是 Unicode 的实现.

##UTF-8

上文提到, Unicode 对所有的字符编码均需要 4 字节, 对拉丁字母或汉字来说, 前2-3个字节是0, 非常浪费. 另外一个问题, 计算机怎么知道四个字节表示一个 Unicode 中的字符, 还是分别表示4个 ASCII 字符?

以上两个问题, 直至 UTF-8 作为 Unicode 的一种实现后, 才部分解决, 得以完成推广使用. UTF-8 的设计者叫 Ben Thomason, 据说是在餐巾纸上设计出来的. 关于 UTF-8 的基本规则, 阮一峰老师总结为两条:

规则1: 对于单字节字符, 字节的第一位为0, 后7位为这个符号的 Unicode 码, 所以兼容 ASCII.

规则2: 对于n字节的字符, 第一个字节前n位都设为1, 第n+1位为0, 后面字节的前两位一律设为10, 剩下没有提及的位, 全部为这个符号的 Unicode 编码。

原始码(16进制)  UTF-8编码(二进制)
0000 − 007F 0 xxxxxxx
0080 − 07FF 110 xxxxx 10 xxxxxx
0800 − FFFF 1110 xxxx 10 xxxxxx 10 xxxxxx
  • ASCII码 0x00-0x7F, 编为 1 个字节的 UTF-8 码.
  • 汉字的 Unicode 编码范围为 0x0800-0xFFFF, 所以被编为 3 个字节的 UTF-8 码.

一段根据 UTF-8 字符获得 Unicode 的 C 代码:

int _get_unicode(const char* str, int n)
{
    int i;
    int unicode = str[0] & ((1 << (8-n)) - 1);
    for (i=1; i<n; i++) {
        unicode = unicode << 6 | ((uint8_t)str[i] & 0x3f);
    }
    return unicode;
}

int get_unicode(char** utf8, int* unicode)
{
    uint8_t c;
    if (!utf8 || !(*utf8) || !unicode) {
        return -1;
    }
    if (**utf8 == 0) {
        return -1;
    }
    c = (*utf8)[0];
    if ((c & 0x80) == 0) {
        *unicode = _get_unicode(*utf8, 1);
        *utf8 += 1;
    } else if ((c & 0xe0) == 0xc0) {
        *unicode = _get_unicode(*utf8, 2);
        *utf8 += 2;
    } else if ((c & 0xf0) == 0xe0) {
        *unicode = _get_unicode(*utf8, 3);
        *utf8 += 3;
    } else if ((c & 0xf8) == 0xf0) {
        *unicode = _get_unicode(*utf8, 4);
        *utf8 += 4;
    } else if ((c & 0xfc) == 0xf8) {
        *unicode = _get_unicode(*utf8, 5);
        *utf8 += 5;
    } else if ((c & 0xfe) == 0xfc) {
        *unicode = _get_unicode(*utf8, 6);
        *utf8 += 6;
    } else {
        return -1;
    }
    return 0;
}

##常见问题

  1. Windows Notepad 中的编码 ANSI 保存选项, 代表什么含义?

ANSI 是 Windows 的默认的编码方式, 对于英文文件是 ASCII 编码, 对于简体中文文件是 GB2312 编码(只针对 Windows 简体中文版, 如果是繁体中文版会采用 Big5码). 所以, 如果将一个 UTF-8 编码的文件, 另存为 ANSI 的方式, 对于中文部分会产生乱码.

2.什么是 UTF-8 的 BOM?

BOM 的全称是Byte Order Mark, 又是微软这货搞出来的特殊东西, 在 UTF-8 编码的文件起始位置, 加入三个字节"EE BB BF", 用于标识文件.

##参考文章