计算机字符编码问题

1. 什么是字符集? 字符编码?

字符集(Character Set)是文字的集合, 如「拉丁字母表」,「简化字总表」 ,「常用國字標準字體表」,等等.

字符编码(Character Encoding)是为了解决计算机处理文字(字母, 数字, 非字母文字, 非文字符号等)而设计的一套规则, 它本质上是文字和其编号的映射表。计算机本质上只能存储0, 1这两个符号, 普通文字要用计算机处理, 就要先对文字编码(编号), 然后在机器里存储使用的都是这个编码. 你可能有个问题, 既然机器计算机处理的是编号, 那么我们为什么看到的不是0, 1而是一个文字呢? 这个问题在后面的字体部分将有解释. 因为历史原因, 具体的一个文字如何编码, 有很多不同的方式, 这就是所谓的「万马(玛)奔腾」. 如数字"3", 字母a, 汉字「文」和符号「☭」(TG的符号), 它们有如下编码:

要得到字符某种类型的编码可以到 Unicode 官网的查询页面, 也可使用 JDK 自带的工具 native2ascii(效果较差), 还可使用这里的 Java 程序完成。

2. 计算机上文件的两种类型

数据通过文件形式存放于计算机. 文件分为文本文件和非文本文件, 本质上它们都以二进制形式存储在计算机上, 但按习惯, 二进制文件特指非文本文件.

参考维基百科词条檔案格式的说法, 要判断一个文件是文本文件还是非文本文件, 是很困难的——没有一定的标准, 只能在具体的应用场景去具体处理. 如判断后缀名, 读文件头的几个字节等等.

一个文本文件使用且只使用一种编码. 当使用 Unicode 时, 可能使用 BOM 技术存放编码信息.

参考:

  1. JavaEye 的帖子: java 中如何判断一个文件是二进制文件还是文本文件?不少回帖都比较有意思, 也反映了判断的困难.
  2. CSDN 的一篇博文: 文本文件和二进制文件的区别 通俗易懂, 娓娓道来.

3. 中文编码

中文字数繁多,要在计算机中使用,就要给每一个汉字对应一个编号,这就是所谓的中文编码。目前在用的中文编码有多种,一种是大陆的 GB 18030, 一种是台湾的 Big5,还有就是九九归一的 Unicode.

3.1 GB 2312

GB 2312 可以认为是一个「数字-汉字」的 map,也可以形象地认为是一张二维大表。0x0000 ~ 0xFFFF 有65535个数字, 理论上可以对应6万多汉字, GB 2312的7445个图形符号(字母, 简体汉字, 图形符号等), 就分布在这个空间上.

  • 一般符号 202
  • 序号 60
  • 数字 22
  • 拉丁字母 52
  • 日文假名 169
  • 希腊字母 48
  • 俄文字母 66
  • 汉语拼音符号 26
  • 汉字 6763 (一级: 3755, 二级: 3008)

编码规则. 对任意图形字符都用两个字节的来表示. 两个字节中前面的字节为第一字节, 后面的字节为第二字节. 第一字节称为区, 第二字节称为位, 区和位的范围都是 1 ~ 94.

这里一个表, 显示了所有 GB 2312 编码(经过 EUC 处理过的)的简体汉字和符号. (浏览器的显示很奇怪, 这个页面上不是所有字符都能显示的. 这个不是很小的一个字集么? 浏览器不是都支持的么? 但是GBK里的一个字符:「镕」, 却能正常显示. 整理完了再研究下是为什么.TODO) 如「啊」字, 编码是 1601, 查出来的是 B0A1.

EUC 存储. 为了和 ASCII 兼容, GB 2312 编码需要转化后存储. 因为 ASCII 代码的范围是: 0000 0000 ~ 0111 1110, 最高位都是0, 为了让 GB 2312 的编码和 ASCII 兼容, 就让 GB 2312 编码的每个字节加上0xA0 = 1000 0000, 使其最高位变成1, 以区别原有 ASCII 代码, 程序读到首位为1的字节后, 就再读一个, 然后作减法, 查表, 就找到相应的汉字编码了. 如上面的「啊」, GB 2312 编码为 1601, 分高低位换成 16 进制:

16 = 0x10, 01 = 0x01

0xA0 + 0x10 = 0xB0

0xA0 + 0x01 = 0xA1

所以「啊」的 GB 2312 码的存储值为 0xB0A1, 用 Java 程序直接求的值也是 EUC 值. 这个问题, 我还在CSDN的论坛上了一下. 要验证一个字的 GB 2312 编码是否正确, 可以使用Windows系统自带的区位码输入法 (Ubuntu也有么? TODO).

官方资源. 标准号: GB 2312-1980. 名称: 信息交换用汉字编码字符集属性. 不爽的是, 它的正版是要收费的.

3.2 GBK

GBK 即「汉字内码扩展规范」(Chinese Internal Code Specification),K即「扩展」的拼音字头. 传说它包含的字数是: 20902 个

GBK 的官方资料很难找, 在国家标准委员会的网站上找到 GB 13000, 居然下载不下来. 鉴于后面的 GB 18030 标准完全兼容 GBK, 这里也就没有再研究下去的动力了.

网上不少地方都称 GBK 来自于 GB 13000, 更有说 GBK 是GB 13000的俗称. 但这个标准下载有问题, 因此我没能看到官方版本, 不过这个标准的 metadata 是查到了, 如下:

标 准 号: GB 13000.1-1993

标准名称: 信息技术 通用多八位编码字符集(UCS) 第一部分:体系结构与基本多文种平面

参考:

  1. 维基百科词条: GB 13000
  2. GBK编码表

问题TODO:

  1. GBK 等同于 Unicode 1.1 吗?
  2. 还有说法是GB 2312 到 GB 13000.1-93的过渡标准.... 是这样吗?

3.3 GB 18030

它是目前中文最新的大陆标准. 该标准的元数据如下:

标 准 号: GB 18030-2005

标准名称: 信息技术 中文编码字符集

GB 18030 标准是由中国大陆的一系列标准(TODO: 需要列出来)发展起来的。它向下与GB2312完全兼容,它包含GB 13000的全部汉字,GB 13000 俗称 GBK。该标准的非附录部分截图在这里.

编码空间(TODO此处需要配图)

单字节 0x00 ~ 0x7F: 0000 0000 ~ 0111 1111

双字节 0x81 ~ 0xFE: 1000 0001 ~ 1111 1110 第一字节

0x40 ~ 0x7E: 0100 0000 ~ 0111 1110 第二字节

0x80 ~ 0xFE: 1000 0000 ~ 1111 1110 第二字节

问题TODO:强标文件中图中的数字1260的来历不清楚。

3.4 BIG5

这是繁体中文的事实标准。它是双字节编码, 也采用 EUC 编码实现。

3.5 Unicode

目前 Unicode 的最新版本是 5.1。它的出现是为了解决各语言编码不相同, 甚至统一语言多种不兼容编码的问题, Unicode 结束了计算机世界处理人类文字的万马(码)奔腾的时代。TODO: 不同的Unicode编码之间的区别和兼容情况, 已经它们之间的转化实现。

一个字符的 Unicode 编码是确定的, 但在存储中可以使用不同的 UTF(Unicode Transformation Format), 比较流行的有UTF-8, UTF-16等。UTF-8 是用不定长的字节来编码的, UTF-16 用双字节编码, 同时有大尾和小尾序的区别。

UTF-8, 首字节的1的个数表示该字符的 UTF 编码的字节数.

此表来自: Java 字符编码

UTF-16, 用双字节或四字节方式编码, 编码范围是 0 ~ 1114111 (=0x10FFFF), 它的编码规则是Unicode值在(0, 0x10000)范围内的字符, 直接用双字节表示, [0x10000, 0x10FFFF)之间的, 用处理后的四字节表示:

此表来自: 维基百科词条 UTF-16

所谓的大尾序(Big Endian)和小尾序(Little Endian), 即上表第二行, x, y 两部分的前后顺序不一样. 以 UTF-16 编码的文件中, 一般会在文件的开头标识BOM(Byte Order Mark), UTF-16LE 是 FFFE, UTF-16BE 是 FEFF.

因为 UTF-16 只用双字节和四字节的编码方式, 所以它无法直接与 ASCII 兼容.

UTF-32, 对所有字符都使用32位定长的编码. 编码空间是 0 ~ 0x7FFFFFF (=2147483647, 21亿)

: 部分程序使用 Unicode 的默认UTF:

TODO: JDK 用的 UTF-16 是 LE 还是 BE?

3.6 编码冲突

@draft

Unicode 内部是否存在编码冲突?(各个 UTF之间, 联系 3.5)

非Unicode编码对其他语言的影响,已经不同汉字编码之间的冲突。

关于各种语言文字编码无冲突共存并能考虑到将要新增语言的发展问题。

GB18030 和 Unicode兼容的情况?不同汉字编码的冲突情况,主要是GB2312和BIG5的冲突情况.

3.7 编码获取

  1. 查询文字编码: http://www.unicode.org/cgi-bin/GetUnihanData.pl
  2. 把要查询的字写到文本文件里, 按相应的编码保存, 用 UE 的16进制模式查看.
  3. Python 的 encode 函数, 比如 >>>"你好".encode("UTF-8")
  4. Java 中可使用native2ascii命令
  5. wiktionary 可能找到一些(不全)

4. 如何检查一个文本文件的编码信息?

1. 如果是 Windows 平台上保存的文本文件, 用记事本程序打开后, 点"文件"-"另存为", 观察其编码.

2. 用 UE 的16进制模式打开, 看最前面的几个字符.

TODO: BOM 的定义和使用.可能是这样检查的, 有BOM信息的, 直接由BOM来判断, 没有的话, 就根据头几个字节的编码情况进行推断(推断有时候也会出错, 比如著名的"联想"事件(需要展开)).

TODO 问题: 在Windows系統中,一個文本文件在保存的時候,有ANSI,UTF8,等等幾種格式。如何識別一個已經存在的文本文件是用什么格式保存的?為什么Linux中沒有這種管理文本文件的方式呢?

TODO 问题, 对于非文本文件如class文件, 怎么它也要选择一种编码来存放?

Windows 平台上的notepad支持的不同编码的文本,是根据文本的前两个字节来定义其编码格式的。定义如下:

ANSI: 无格式定义;

Unicode: 前两个字节为FFFE;

Unicode big endian: 前两字节为FEFF;

UTF-8: 前两字节为EFBB;

参 http://www.haoxiai.net/bianchengyuyan/Delphi/13831.html

如果接收到以下字节﹐则分别表明了该文本文件的编码。

UTF-8: EF BB BF

UTF-16 : FF FE

UTF-16 big-endian: FE FF

UTF-32 little-endian: FF FE 00 00

UTF-32 big-endian: 00 00 FE FF

而如果不是以这个开头﹐那程序则会以 ANSI, 也就是系统默认编码读取。

关于Java的, 参 Unicode文件中文编码解码的实现 http://www.javaeye.com/topic/221174

著名的故事:

「联通」看不到的原因.

「联通」的内码是:

c1 1100 0001

aa 1010 1010

cd 1100 1101

a8 1010 1000

注意到了吗?第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,

TODO: 一个文本文件或含有文字打开被显示成人类可识别的字符的过程可能是这样的: 加载对应字体去显示它. 如何加载正确的字体, 就需要一些判断了. 待后.

5. 其他问题

  1. 矢量汉字和点阵汉字的问题。
  2. 编码和字体的关系(可选, 以及: 字体的工作原理)
  3. Windows机器上的仓颉输入法, 如果用Unicode模式,很多字都无法显示.
  4. 需要下载什么字体文件? Windows 2000有这个问题,看看Windows XP?
  5. 修改eclipse的jsp创建模板,把字符集统一到UTF-8下

6. 后记

之所以讨论了如上的编码问题, 是因为"Java代码的诸编码" 部分引起的问题所致. 整理了以前放在GDocs的文档, 直接结果就是三篇原始文档被删, 为了纪念, 这里是它们临死前的拍照.

7. 参考

  1. 本站关联词条: 字符串的奶酪(String Cheese), Java代码的诸编码
  2. 维基百科词条: 字符集, 编码, EUC, GB 2312, GBK, GB 18030, BIG5, Unicode
  3. 汉字编码查询(包含Unicode)
  4. 国家标准化管理委员会
  5. 中国大陆国标下载阅读
  6. Unicode 官网: http://www.unicode.org/
  7. Unicode官方站点汉字编码查询
  8. JavaEye: Java读带有BOM的UTF-8文件乱码原因及解决方法
  9. BlogJava: 字符编解码的故事(ASCII,ANSI,Unicode,Utf-8) (按: 这是一篇通俗易懂的科普文)
  10. 伟网动力: JAVA字符编码 2004-3-31