最近在做一个物联网协议编解码的工作,需要将字符内容根据私有协议编码为二进制以字节流发送到终端。在编码手机号码时直接使用了 String 的 getBytes() 方法,但当终端程序员问我该怎么解码这段二进制内容时,我开始思考自己对字符编码的理解。
一开始我想直接回答,按 ASSIC 编码去解成手机号就行了,但是这个解释严谨吗?显然一串数字号码通过 String 的 getBytes() 方法编码后看起来确实以 ASSIC 编码的,但是事实上由于编程的疏忽,使用不指定编码格式的 getBytes() 无参方法得到的字节数组,是以操作系统默认使用的编码方式进行编码的,按照项目协议的约定,应该使用 GBK 编码,改为 getBytes("GBK")。GBK 编码可不能说成 ASSIC 编码。
那么问题来了,GBK 编码和 ASSIC 编码是什么关系?为什么 GBK 编码的手机号跟 ASSIC 编码出来的一样?其实 GBK 和 ASSIC 是两种字符编码,ASSIC 使用一个字节的低 7 位编码字符,最高位总是 0。ASSIC 是最早使用的一种编码方式,只能编码 2^7=128 个英语国家常用的字符。中国人为了编码汉字(简体中文)制定了 GBK 编码,许多文章甚至书籍将 GBK 描述为固定使用 2 个字节进行编码,但其实 GBK 只使用双字节编码中文字符,为了兼容 ASSIC 编码,编码 ASSIC 字符时只使用 1 个字节。这听起来有点绕,可以理解为 GBK 编码规范使用了、包含了 ASSIC 编码规范。问题又来了,计算机使用 GBK 编码解读字符串时,如何判断一个字节是一个中文字符的双字节表示的一半,还是一个完整的 ASSIC 字符呢?其实 GBK 编码的第一个字节的最高位必为 1,而前面说到 ASSIC 编码的字节最高位是 0,所以当计算机先读到最高位是 1 的字节时,就知道这个字节和下一个字节组成一个中文字符,否则是 ASSIC 字符。
其实不只是 GBK 编码,大部分自然语言的计算机编码也都兼容 ASSIC 编码。ASSIC 看起来是非常通用的字符编码,而 Unicode 似乎也是非常通用的“编码”,那么 ASSIC 和 Unicode 是什么关系?其实 Unicode 跟 ASSIC 完全不是一回事,如果说 ASSIC 是计算机字符编码里的最小集,那 Unicode 就是试图编码字符最大集的一套编码标准,Unicode 定义了世界上常用的(中英日等)大部分文字和符号的编号,大部分可以用2个字节表示,但是 Unicode 并不像 GBK、ASSIC 这样规定了字符的二进制表示方式,如何用二进制编码表示 Unicode 定义的字符编号,由 UTF-8 这样的具体实现标准去定义。
我们熟知的 UTF-8、UTF-16 的前缀 UTF,全称Unicode Transformation Format(Unicode 转换格式),是 Unicode 真正的编码实现。它们都是为了将字符的 Unicode 编号编码为二进制,但是 UTF-16 会使用2到4个字节去编码;而 UTF-8 可以更节省空间,使用1到4个字节编码。UTF-8 目前更被广泛使用,它兼容 ASSIC 编码,表示 ASSIC 字符时只使用1个字节,表示汉字时通常使用3个字节。有些国产程序不考虑国际化,使用 GBK 编码,表示汉字时只需要2个字节,相比 UTF-8 更节约空间。
平常在面向字符编程时对字符编码的原理不太关注,等到面向二进制字节编程时才发现有些原理自己并不能一下子说清。特此梳理,自勉。