« 在亚龙湾晒太阳 | 返回首页 | 关于词典软件 »

一种对汉字更环保的 Unicode 编码方案

Windows 早期默认支持的中文编码方案是 GBK ,它是对早期 GB2312 的一个扩展。后来国家又发布了 GB18030 标准,扩展了 GBK ,增加了一些 Unicode 里才有的汉字,这样就可以做到和 Unicode 字符集做完全的映射了。

而后来 Unicode 逐渐成为标准,几乎所有的系统处理多国语言时,都把 Unicode 做为默认设置了。Unicode 有两套编码集,UCS-2 和 UCS-4 ,后者是对前者的补充,虽说字面说写的 4,但实际上只用到了 3 个字节不到。

Windows 的内部其实是用的 UCS-2 标准,并用 UTF-16 来实现。而非 Windows 系统大多采用了 UTF-8 。UTF-8 在很多情况下更有优势,首先它不需要考虑大头小头的问题,其次,数据损坏的时候,不会有半个汉字的问题。并且 UTF-8 可以完整的表达 UCS-4 而不需要有额外的付出。

前几年,处于 Windows 程序员转型期,经过一些痛苦的实践,我终于意识到,VC 提倡的所谓为软件维护 UNICODE 和 非 UNICODE 两个版本是件巨傻 X 的事情。之后,我们的程序再也不为内码分版本了,一律采用 UTF-8

不过,UTF-8 的设计明显是用英文为主的西方人搞出来的东西。对于中文一点都不环保。所有汉字和中文标点都需要 3 个字节才能表达。而少量欧洲字母可以用 2 字节表达,英文的 ASCII 符号则可以只用单字节。

我不指责这个设计,因为兼容 ASCII 是 UTF-8 的最大优势。其它,则是 KISS 原则下的产物。

不过,若是内部使用的话,如果你介意这 1/3 的储存空间的浪费的话,是不是有改进的余地呢?下面我们来讨论这个问题。

观察 Unicode 的码表我们可以发现,实际大部分汉字是从 U+4E00 开始编码的,到 U+9FFF 结束。U+2000 到 U+3FFF 则有一些汉字标点符号等。稍加设计,我们可以定义出一种编码方案,对 UCS-2 或 UCS-4 做编码,让汉字可以用双字节表达,并保持对 ASCII 的兼容(U+0000 到 U+007F)段依然使用单字节。

作为牺牲,那些欧洲字母和韩文,就委屈到 3 字节甚至更长的编码了。

简述一下对 UCS-2 的编码方案(UCS-4 只需要做点小扩展)。

  1. 使用 大头方案(Big-endian)

  2. ASCII 部分 U+0000 到 U+007F 保留,用单字节 0x00 ~ 0x7f 表达。

  3. 对于 U+2000 到 U+9FFF ,除掉其中的 U+3F00 到 U+3FFF 部分(目前这个部分为空),做一个简单变换:即加上 0x6000 ,把编码转换到 0x8000 到 0xffff 部分。做双字节编码。由于 U+3F00 到 U+3FFFF 被除外(如果是 UCS-4 可以去除 U+3E00 到 U+3FFF),所以 0x9f 将成为一个 magic number 。

  4. 对于其它部分:U+0080 到 U+1FFF ,U+3F00 到 U+3FFF ,U+A000 到 U+FFFF ,一律编码到第 2 3 字节。并将第一字节直接设为 0x9F 。

这样,解码成 UCS-2 的算法非常简单(甚至不比 UTF-8 解码更复杂)。缺点是,缺乏 UTF-8 的容错性,有半个汉字的隐患。


这样的编码什么时候会有用?

  1. 网络传输时,对字符串做这样的编码(写一个 UTF-8 到这个特定编码方案的互转程序可以在 10 行 C 代码内完成,并非常高效),可以有效减少传输的数据。

  2. 如果需要做大规模的数据储存,比如搜索引擎抓取的网页时,可以节省不少储存空间。

至于源代码,和配置文件中,还是推荐使用 UTF-8 ,毕竟编辑器的支持更重要。


C 的实现见这里

Comments

有点难理解

弱弱的问一下,为什么把汉字的那个部分要做转换。加上0x60。直接编码成两个字节不行吗

每个算法都有其特定的应用背景,云风在22楼很好的解释了这一编码的应用情况。该冒泡的时候冒泡、该快排的时候快排、该桶排的时候桶排,没有一个万能的方案

我想你对UCS-2和UTF-16的定义搞错了,其实它们都是编码,在U+FFFD之前甚至是一模一样的。区别是UCS-2只是双字节,可容纳65534字符,UTF-16是双字节或者四字节,可以容纳更多字符。由于不支持超过U+FFFD的unicode,UCS-2已经被淘汰了,不过Windows使用的Unicode就是UCS-2。

Wikipedia下的解释:
UCS-2 (2-byte Universal Character Set) is an obsolete character encoding which is a predecessor to UTF-16. The UCS-2 encoding form is identical to that of UTF-16, except that it does not support surrogate pairs and therefore can only encode characters in the BMP range U+0000 through U+FFFF. As a consequence it is a fixed-length encoding that always encodes characters into a single 16-bit value. As with UTF-16, there are three related encoding schemes (UCS-2, UCS-2BE, UCS-2LE) that map characters to a specific byte sequence.

Because of the technical similarities and upwards compatibility from UCS-2 to UTF-16, the two encodings are often erroneously conflated and used as if interchangeable, so that strings encoded in UTF-16 are sometimes misidentified as being encoded in UCS-2.

哈 , magic number 那里是个 bug, 已经改过来了 :) 谢谢

发现一点小问题:

ASCII字符:
U+0000 .. U+007F 不变,单字节范围00..7F

汉字:
U+2000 .. U+2EFF 高位+60,首字节范围80..8E
U+3000 .. U+9FFF 高位+60,首字节范围90..FF

这里9F被90..FF覆盖了,
首字节可选的只有8F了.

题目写错了,应该是实现了一种转换格式(Unicode Translation Format)吧?
对于数据传递/保存来讲,汉字确实比较占空间(别拿MS的ansi格式说事哦):如果用UTF16,那纯英文文件就大了两倍(一位变成了两位),纯汉字大小不变;如果用UTF8,那纯汉字文件就多了一半空间(两位变成了三位),纯英文大小不变.这样一来作者的转换方案就实现了只要内容是汉字和英文组合,大小都保持不变了.
BTW:大家讨论的都对不上主题
1)UTF跟文本压缩根本就不是一回事,压缩总有效率问题,在高并发的时候服务器多了不少负担
2)如果是读入UTF8格式串,系统本身也是需要转换成unicode的;现在只是替换成用自己的转换来读取unicode,只不过同时为了通过用也提供了自己的格式跟utf8互相转换?

UCS 和 UTF 是不同的东西。

如果是为了容错且保持对 ascii 的兼容性,用 UTF-8 最好了。尤其在文件名等特定场合更是如此。

内存中的文本处理,我想,如果涉及中文比较多的情况下,UTF-16 最佳。以 ascii 为主时,UTF-8 更好。其它 UTF 方案都不适合放在内存的数据结构中。

所以,即使有特定的需求,设计新的 UTF ,也以方便转换到 UTF-16 或 UTF-8 为主要指导。

有点混乱。
既然说的是类似UCS-2的编码,那么自然是以16bit为单位进行的编码,又怎么会有让ASCII保持单字节,而某些区域用3个字节,这种东西?
如果说是以8bit为单位的编码,那么就不应该让ASCII的0值存在其中,因为这样的串很多8bit的系统并不支持,比如C语言里ASIIZ字符串,是以0值为结尾的,这种字节串就难以表达。此外低字节也有很多码值属于控制字符,也是一般8bit软件忌讳的,比如文件名等场合,都不合适。

我之前有过类似设想的文章,让汉字变为2个字节。参见:
http://www.pkucn.com/viewthread.php?tid=164480

每个算法都有其特定的应用背景,云风在22楼很好的解释了这一编码的应用情况。该冒泡的时候冒泡、该快排的时候快排、该桶排的时候桶排,没有一个万能的方案

我认为使用压缩技术,比新搞个编码更KISS。

GB18030就是一种UTF啊,何必自己发明?

嗯,这样的编码对上层应用基本是透明,也谈不到什么兼容性的问题。在一些嵌入式设备上看到过类似的解决方案。

题目误导了

也许应该改成“一种对汉字更环保的文本流编码方案”

@微子

UTF 和 UCS 是不同层面的东西,Unicode (指 UCS) 本身并不描述具体编码方案。

所以不存在所谓 UTF 到 Unicode 的转换表 。(没有也不需要这样的一张表)

握手协议是另一件事,跟我前面回复说的 ip 包头等是一件事。

如果网游的例子不合适的话,可以考虑各种 RPC 协议。即高频交互的长连接上的通讯数据。

看来我曲解了你的中心意思, 但仍然解释一下.

一段较长信息流的最优压缩是Huffman编码(与熵, 本质都是排序不等式). 几百千字节以上的文件其压缩大小不太依赖于编码. 但小文件及传输确实有影响. 不过, 小的很的话, 怀疑握手协议流就比文件大了.

你可以写一个 UTF-C 到Unicode的转换表, 生成 locale数据, 然后编辑器什么的就可以用了.


虽然我不太愿意局限在数据压缩层面讨论这个问题。不过既然谈到了,可以说说实际的功用。

比如在网游里,采用 RPC (也可以不是 RPC) 通讯。

网游的包的特点是短小,高频。可能每个连接每次就发一个短字符串,姑且假设有 4 个汉字。那么,UTF-8 编码就是 12 字节。如果我们换种编码,可能就是 8 字节。

但这种短小的包是不能用通用算法压缩的。btw, 关于这个方面的讨论,我曾经也写过一篇 blog : 基于TCP数据流的压缩 http://blog.codingnow.com/2006/01/tcp_stream_compress.html

目前用在大话服务器里。

对于一个连接来说,少了 4 个字节没什么。但是一台服务器同时可能要处理 1000 ~ 10000 个连接。这样带宽就不一样了。

当然,我们没有讨论关于 ip 包封装等等问题。只是指理论带宽消耗。

对于标准压缩算法, 也有 CPU 消耗的问题,内存占用问题等等。

把问题局限在压缩数据方面不是我的本意,也不用在这方面多纠结。

我是想说,UCS 是 UCS , UTF 是 UTF 。可以有 UTF-8 也可以用 UTF-C (UTF-C 我生造的词, 表示对 C 中文的优化)。

特定的 UCS Transformation Format 在特定的场合有自己的优势。这跟压缩数据是两个层次的问题。用了特定的 UTF 并不妨碍继续用数据压缩。

关于实现,我想不超过 10 行 C 代码就可以完成了。只需要大学本科毕业,学习过 1 年 C 语言编程的学生就可以。而且可以保证没有错误。当然,对于已经不再写算法实现的程序员或者"构架师"来说可能有畏惧感。这些人或许写 UTF-8 解码的程序都难以一次写对,必须求助于 google 或开源社区寻求现成的代码。

抛开实现层来说,这是个相对基础的底层工作。好比你有了一个 XML 解析库,工作在上面就可以了。不需要每个人都去解析 XML 。

云哥的这个建议非常不妥。假如为压缩网络传输数据量,直接压缩就行了,并具有最高的兼容性,假如做为Doc,可被识别最重要,也就是说,可以把9f提案做为建议标准,直到被标准采纳前,依然不应该提倡这样的用法——百害无一利的。

又仔细看了一下回复。似乎楼主的目的是减少网络传送时的数据量。

我觉得文章的切入点有些问题:应该是在网络传输层上叠加一个针对特定字符编码的压缩协议,而不是新建一个新的字符编码。这样的话,这种编码就应该仅仅存在于传输层面,用户的输入和输出还是符合标准utf8编码的。

不认同这种方法,太麻烦了。估计加一层gzip或者lzw的压缩算法就能达到比这个效果好的压缩率。

最关键的问题,utf8是通用的,而这种格式除了自己写的程序,别人谁也不认识。

未必有什么意义。

计算机的存储设备、网络带宽的价格只会越来越便宜,跟上证指数差不多。但是,不变的、昂贵的仍然是人力资源成本。

设计出这么一套编码方案,当其他人继续开发时,需要额外的培训(花钱)、额外的代码(更花钱,想想平均一行代码所花的费用是多少)、额外的测试(花钱。不仅是功能,还有兼容性)......而且,每新来一个员工都得接受额外的培训,花更多的时间熟练,才能应用,这花的钱可不一般多。而老员工一离职,已经投入的钱也就扔到水里去了。

与其这样,不如继续用那些成熟的方案吧,至少,一般的程序员不需要培训,也能写出适合那些成熟方案的代码;测试也更容易。这样省下的钱或许比少买几块硬盘、内存或者少用几K带宽省下的钱要多多了。

竟然在cnbeta网站看到你的文章了哦

http://www.cnbeta.com/articles/73166.htm

Merry christmas!
我订阅了云风的博客,好方便哦,及时看到最新文章

这样的话可以减少1/3的文字传输量了.

要弄清楚 UCS 和 UTF 的区别。文本压缩和 Transformation Format 的区别。

Unicode 定义的是数字和字符的对应关系,如何数字信息如何编码成字节是另一个层次的故事。

GB18030 和 Unicode 是不同的编码。

文字传输占据了网游通讯中至少 1/3 的流量。

这样改了还能出口到越南或菲律宾吗?

这个方案是GBK2009,而不是Unicode;讲紧凑那么UTF-16就可以了,只是不兼容ASCII;至于大尾序还是小尾序不是什么大事,实在不行有BOM呢。

感觉作者这样考虑真是费力不讨好,txt对网络资源的占用与声音图像视频比,完全可以忽略不计了。

报告错别字——第四段“过度期” :)

那干嘛不直接使用GB18030-2005,可以和UCS-4互相转换

在windows下,内部处理用utf-16是最佳的编码方案,尤其是在处理正则表达式的时候,用utf-16要省心很多。而存储和传输的时候还是推荐用utf-8。

学习了。
几乎所谓的系统处理多国语言时
几乎所有?

其实有着类似文本压缩软件的思想。但编解码效率要高一些。但压缩率应该不到50%吧。那么这样在存储和传输中比起直接用流行的文本压缩算法有何优势呢?(Unicode最大的优势是已经成为标准,但个人制定的编码方案肯定得不到其他软件的支持,所以个人觉得这样做在实际使用上还值得商榷)

Merry christmas!
早上就来看楼主的文章了

@微子

首先要认清 UCS 和 UTF 概念上的不同。

本文提出的编码方案,并没有改变 Unicode ,所以并没有"破坏多语言统一" 。

Unicode 的本质是对多国语言的符号向数字做一个统一的映射。

对于中文还是别的语言,Unicode 本身的设计没有偏袒。

仅从压缩角度考虑,"不同编码完全不改变信息的熵, 在极限情况下, 不会给压缩带来改变." 实际上是不成立的。

因为理论的信息熵是不可计算的(就是所谓的极限的不可得到的),但在实际数据处理中,压缩算法得到的信息熵会因为对信息的了解程度而变化。

另一方面,以不同的方案去编码 Unicode 绝不仅仅是为了储存考虑。


不太同意这种破坏多语言统一的做法.

你真实怨念的是, 中文不在设计的中心.

觉得, 这些浪费的空间完全可以再加层压缩来平衡. 不同编码完全不改变信息的熵, 在极限情况下, 不会给压缩带来改变.

所谓信息理论中提到的压缩极限是有前提的。你对你的信息了解的更多,就更能减少信息熵。

比如,你给出一种随机算法,和一个随机种子,就可以用很少的信息量保存一大段数据。反之,用不了解这个信息的方法去压缩数据,压缩率就会很小。

故而,“随便一种简单编码的方案”能解决的东西和文中的表达的方法是两个问题。

为什么 UTF-7 UTF-8 UTF-16 UTF-32 会并存,正是因为 Unicode 编码在不同环境下需要有不同的编码方案。

虽然4个字节表示一个字,但信息熵没有增加多少,随便一种简单编码的方案就可以接近实现理论上的压缩极限了

Post a comment

非这个主题相关的留言请到:留言本