浅谈文字编码和Unicode
浅谈文字编码和Unicode(上)
引自感谢伐木丁丁鸟鸣嘤嘤
我曾经写过一篇《谈谈Unicode编码简要解释UCS、 UTF、 BMP、 BOM等名词》 (以下简称《谈谈Unicode编码》 ) 在网上流传较广我也收到不少朋友的反馈。本文探讨《谈谈Unicode编码》中未介绍或介绍较少的代码页、Surrogates等问题补充一些Unicode资料顺带介绍一下我最近编写的一个Unicode工具 UniToy。本文虽然是前文的补充但在写作上尽量做到独立成篇。
标题中的"浅谈"是对自己的要求我希望文字能尽量浅显易懂。但本文还是假设读者知道字节、 16进制 了解《谈谈Unicode编码》中介绍过的字节序和Unicode的基本概念。
0 UniToy UniToy是我编写的一个小工具。通过UniToy我们可以全方位、多角度地查看Unicode 了解Unicode和语言、代码页的关系完成一些文字编码的相关工作。本文的一些内容是通过UniToy演示的。大家可以从我的网站()下载UniToy的演示版本。
1文字的显示
1. 1发生了什么?
我们首先以Windows为例来看看文字显示过程中发生了什么。用记事本打开一个文本文件可以看到文件包含的文字
如果我们用UltraEdit或Hex Workshop查看这个文件的16进制数据可以看到
我们看到文件"例子GBK. txt"有10个字节依次是"D7 D6 B7 FB BA CDB1 E0 C2 EB"这就是记事本从文件中读到的内容。记事本是用来打开文本文件的所以它会调用Windows的文本显示函数将读到的数据作为文本显示。Windows首先将文本数据转换到它内部使用的编码格式 Unicode然后按照文本的Unicode去字体文件中查找字体图像最后将图像显示到窗口上。总结一下前面的分析文字的显示应该是这样的
步骤1文字首先以某种编码保存在文件中。步骤2 Windows将文件中的文字编码映射到Unicode。步骤3 Windows按照Unicode在字体文件中查找字体图像画到窗口上。所谓编码就是用数字表示字符例如用D7D6表示"字"。当然编码还意味着约定 即大家都认可。从《谈谈Unicode编码》中我们知道Unicode也是一种文字编码它的特殊性在于它是由国际组织设计可以容纳全世界所有语言文字。而我们平常使用的文字编码通常是针对一个区域的语言、文字设计只支持特定的语言文字。例如在上面的例子中文件"例子GBK. txt"采用的就是GBK编码。
如果上述3个步骤中任何一步发生了错误文字就不能被正确显示例如
错误1如果弄错了编码例如将Big5编码的文字当作GBK编码就会出现乱码。
错误2如果从特定编码到Unicode的映射发生错误例如文本数据中出现该编码方案未定义的字符 Windows就会使用缺省字符通常是?。
如果当前字体不支持要显示的字符 Windows就会显示字体文件中的缺省图像空白或方格。在Unicode被广泛使用前有多少种语言、文字就可能有多少种文字编码方案。一种文字也可能有多种编码方案。那么我们怎么确定文本数据采用了什么编码?
1.2采用了哪种编码?
按照惯例文本文件中的数据都是文本编码那么它怎么表明自己的编码格式?在记事本的"打开"对话框上
我们可以看到记事本支持4种编码格式 ANSI、 Unicode、 Unicode bigendian、 UTF-8。如果读者看过《谈谈Unicode编码》 对Unicode、 Unicodebig endian、 UTF-8应该不会陌生其实它们更准确的名称应该是UTF-16LE(Little Endian) 、 UTF-16BE(Big Endian)和UTF-8它们是基于Unicode的不同编码方案。
在《谈谈Unicode编码》中介绍过 Windows通过在文本文件开头增加一些特殊字节(BOM)来区分上述3种编码并将没有BOM的文本数据按照ANS I代码页处理。那么什么是代码页什么是ANS I代码页?
2代码页和字符集
2. 1 Windows的代码页
2. 1. 1代码页
代码页(Code Page)是个古老的专业术语据说是IBM公司首先使用的。代码页和字符集的含义基本相同代码页规定了适用于特定地区的字符集合和这些字符的编码。可以将代码页理解为字符和字节数据的映射表。
Windows为自己支持的代码页都编了一个号码。例如代码页936就是简体中文GBK代码页950就是繁体中文Big5。代码页的概念比较简单就是一个字符编码方案。但要说清楚Windows的ANSI代码页就要从Windows的区域(Locale)说起了。
2. 1.2区域和ANS I代码页
微软为了适应世界上不同地区用户的文化背景和生活习惯在Windows中设计了区域(Locale)设置的功能。 Local是指特定于某个国家或地区的一组设定包括代码页数字、货币、时间和日期的格式等。在Windows内部其实有两个Locale设置系统Locale和用户Locale。系统Locale决定代码页用户Locale决定数字、货币、时间和日期的格式。我们可以在控制面板的"区域和语言选项"中设置系统Locale和用户Locale
每个Locale都有一个对应的代码页。 Locale和代码页的对应关系大家可以参阅我的另一篇文章《谈谈Windows程序中的字符编码》的附录1。系统Locale对应的代码页被作为Windows的默认代码页。在没有文本编码信息时Windows按照默认代码页的编码方案解释文本数据。这个默认代码页通常被称作ANSI代码页(ACP) 。
ANS I代码页还有一层意思就是微软自己定义的代码页。在历史上 IBM的个人计算机和微软公司的操作系统曾经是PC的标准配置。微软公司将IBM公司定义的代码页称作OEM代码页在IBM公司的代码页基础上作了些增补后作为自己的代码页并冠以ANS I的字样。我们在"区域和语言选项"高级页面的代码页转换表中看到的包含ANS I字样的代码页都是微软自己定义的代码页。例如
874(ANS I/OEM-泰文)932(ANS I/OEM-日文Shi f t-JIS)936(ANS I/OEM-简体中文GBK)949(ANS I/OEM-韩文)950(ANS I/OEM-繁体中文Bi g5) 1250(ANS I-中欧) 1251 (ANS I-西里尔文) 1252(ANS I-拉丁文I) 1253(ANS I-希腊文) 1254(ANS I-
土耳其文) 1255(ANS I-希伯来文) 1256(ANS I-阿拉伯文) 1257(ANS I-波罗的海文) 1258(ANSI/OEM-越南)在UniToy中我们可以按照代码页编码顺序查看这些代码页的字符和编码
我们不能直接设置ANSI代码页只能通过选择系统Locale 间接改变当前的ANSI代码页。微软定义的Locale只使用自己定义的代码页。所以我们虽然可以通过"区域和语言选项"中的代码页转换表安装很多代码页但只能将微软的代码页作为系统默认代码页。
2. 1.3代码页转换表
在Windows 2000以后 Windows统一采用UTF-16作为内部字符编码。现在安装一个代码页就是安装一张代码页转换表。通过代码页转换表 Windows既可以将代码页的编码转换到UTF-16也可以将UTF-16转换到代码页的编码。代码页转换表的具体实现可以是一个以nls为后缀的数据文件也可以是一个提供转换函数的动态链接库。有的代码页是不需要安装的。例如 Windows将UTF-7和UTF-8分别作为代码页65000和代码页65001。 UTF-7、 UTF-8和UTF-16都是基于Unicode的编码方案。它们之间可以通过简单的算法直接转换不需要安装代码页转换表。
在安装过一个代码页后 Windows就知道怎样将该代码页的文本转换到Unicode文本也知道怎样将Unicode文本转换成该代码页的文本。例如UniToy有导入和导出功能。所谓导入功能就是将任一代码页的文本文件转换到Unicode文本导出功能就是将Unicode文本转换到任一指定的代码页。这里所说的代码页就是指系统已安装的代码页
其实如果全世界人民在计算机刚发明时就统一采用Unicode作为字符编码那么代码页就没有存在的必要了。可惜在Unicode被发明前世界各国人民都发明并使用了各种字符编码方案。所以 Windows必须通过代码页支持已经被广泛使用的字符编码。从这种意义看代码页主要是为了兼容现有的数据、程序和习惯而存在的。
2. 1.4 SBCS、 DBCS和MBCS SBCS、 DBCS和MBCS分别是单字节字符集、双字节字符集和多字节字符集的缩写。 SBCS、 DBCS和MBCS的最大编码长度分别是1字节、两字节和大于两字节(例如4或5字节) 。例如代码页1252(ANS I-
拉丁文I)是单字节字符集代码页936(ANSI/OEM-简体中文GBK)是双字节字符集代码页54936(GB 18030简体中文)是多字节字符集。
单字节字符集中的字符都用一个字节表示。显然 SBCS最多只能容纳256个字符。
双字节字符集的字符用一个或两个字节表示。那么我们从文本数据中读到一个字节时怎么判断它是单字节字符还是双字节字符的首字符?答案是通过字节所处范围来判断。例如在GBK编码中单字节字符的范围是0x00-0x80双字节字符首字节的范围是0x81到0xFE。我们顺序读取字节数据如果读到的字节在0x81到0xFE内那么这个字节就是双字节字符的首字节。 GBK定义双字节字符的尾字节范围是0x40到0x7E和0x80到0xFE。
GB 18030是多字节字符集它的字符可以用一个、两个或四个字节表示。这时我们又如何判断一个字节是属于单字节字符双字节字符还是四字节字符?GB18030与GBK是兼容的它利用了GBK双字节字符尾字节的未使用码位。GB18030的四字节字符的第一字节的范围也是0x81到0xFE第二字节的范围是0x30-0x39。通过第二字节所处范围就可以区分双字节字符和四字节字符。GB18030定义四字节字符的第三字节范围是0x81到0xFE第四字节范围是0x30-0x39。
2.2代码页实例
2.2. 1实例一 GB 18030代码页
1. 1节的"错误2"中演示了一个全被显示成'?'的文件。这个文件的数据是
其实这是一个包含了6个四字节字符的GB 18030编码的文件。记事本按照GBK显示这些数据而GB18030的四字节字符编码在GBK中是未定义的。Windows根据首字节范围判断出12个双字节字符然后因为找不到匹配的转换而将其映射到默认字符'?' 。使用UniToy按照GB18030代码页导入这个文件就可以看到
这个GB18030编码的文件是用UniToy创建的编辑Unicode文本然后导出到GB 18030编码格式。
2.2.2实例二 GBK和Big5的转换
综合使用UniToy的导入、导出功能就可以在任意两个代码页之间转换文本。其实 由于各代码页支持的字符范围不同我们一般不会直接在代码页间转换文本。例如将以下GBK编码的文本
直接转换到Big5编码就会看到
变成'?'的字符都是Big5编码不支持的简化字。在从Unicode转换到Big5编码时 由于Big5编码不支持这些字符 Windows就用默认字符'?'代替。在UniToy中我们可以先将简体字转换到繁体字然后再导出到Big5编码就可以正常显示
同理将Big5编码的文本转换到GBK编码的步骤应该是
将Big5编码的文本导入到Unicode文本将繁体的Unicode文本转换简体的Unicode文本将简体的Unicode文本导出到GBK文本。 2.3互联网的字符集
2.3. 1字符集
互联网上的信息缤纷多彩但文本依然是最重要的信息载体。 html文件通过标记表明自己使用的字符集。例如meta http-equiv="Content-Type"content="text/html charset=utf-8"
或者meta http-equiv="charset"content="iso-8859-1"
那么我们可以使用哪些字符集(charset)呢?在IETF(互联网工程任务组)的网页上维护着一份可以在互联网上使用的字符集的清单 CHARACTER SETS。如果有新的字符集被登记 IETF会更新这份文档。
简单浏览一下 2006年12月7日的版本列出了253个字符集。其中也包括微软的CP1250~CP1258在这里它们不会被称作什么ANSI代码页而是被简单地称作windows-1250、 windows-1251等。其实在Unicode被广泛使用前除了中日韩等大字符集世界上特别是西方使用最广泛的字符集应该是ISO8859系列字符集。
2.3.2 ISO 8859系列字符集
ISO 8859系列字符集是欧洲计算机制造商协会(ECMA)在上世纪80年代中期设计并被国际标准化(ISO)组织采纳为国际标准。 ISO 8859系列字符集目前有15个字符集包括
ISO 8859-1大部分的西欧语系例如英文、法文、西班牙文和德文等(Latin-1)ISO 8859-2大部分的中欧和东欧语系例如捷克文、波兰文和匈牙利文等(Latin-2)ISO 8859-3欧洲东南部和其它各种文字(Latin-3)ISO 8859-4斯堪的那维亚和波罗的海语系(Latin-4)ISO 8859-5拉丁文与斯拉夫文(俄文、保加利亚文等)ISO 8859-6拉丁文与阿拉伯文ISO 8859-7拉丁文与希腊文ISO8859-8拉丁文与希伯来文ISO 8859-9为土耳其文修正的Latin-1 (Latin-5)ISO8859-10拉普人、北欧与爱斯基摩人的文字(Latin-6)ISO 8859-11拉丁文与泰文ISO 8859-13波罗的海周边语系例如拉脱维亚文等(Latin-7)ISO 8859-14凯尔特文例如盖尔文、威尔士文等(Latin-8)ISO 8859-15改进的Latin-1增加遗漏的法文、芬兰文字符和欧元符号(Latin-9)ISO 8859-16罗马尼亚文(Latin-10)其中缺少的编号12据说是为了预留给天城体梵文字母(Deva-nagari)的。印地文和尼泊尔文都使用了这种在七世纪形成的字母表。 由于印度定义了自己的编码ISCII(Indian Script Code for Information Interchange) 所以这个编号就未被使用。 ISO 8859系列字符集都是单字节字符集 即只使用0x00-0xFF对字符编码。
大家都知道ASCII吧那么大家知道ANSI X3.4和ISO 646吗?在1968年发布的ANSI X3.4和1972年发布的ISO 646就是ASCII编码只不过是不同组织发布的。绝大多数字符集都与ASCII编码保持兼容 ISO 8859系列字符集也不例外它们的0x00-0x7f都与ASCII码保持一致各字符集的不同之处在于如何利用0x80-0xff的码位。使用UniToy可以查看ISO 8859系列所有字符集的编码例如
通过这些演示大家是不是觉得代码页和字符集都是很简单、朴实的东西呢?好在进入Unicode的话题前让我们先看一个很深奥的概念。
浅谈文字编码和Unicode(下)
3字符编码模型
程序员经常会面对复杂的问题而降低复杂性的最简单的方法就是分而治之。 Peter Constable在他的文章"Character set encoding basics
Understanding character set encodings and legacy encodings"中描述了字符编码的四层模型。我觉得这种说法确实可以更清晰地展现字符编码中发生的事情所以在这里也介绍一下。
3. 1字符的范围(Abstract character repertoire)
设计字符编码的第一层就是确定字符的范围 即要支持哪些字符。有些编码方案的字符范围是固定的例如ASCII、 ISO 8859系列。有些编码方案的字符范围是开放的例如Unicode的字符范围就是世界上所有的字符。
3.2用数字表示字符(Coded character set)
设计字符编码的第二层是将字符和数字对应起来。可以将这个层次理解成数学家(即从数学角度)看到的字符编码。数学家看到的字符编码是一个正整数。例如在Unicode中汉字"字"对应的数字是23383。汉字""对应的数字是
134192。
在写html文件时可以通过输入"字 "来插入字符"字"。不过在设计字符编码时我们还是习惯用16进制表示数字。即将23383写成0x5BD7将134192写成0x20C30。
3.3用基本数据类型表示字符(Character encoding form)
设计字符编码的第三层是用编程语言中的基本数据类型来表示字符。可以将这个层次理解成程序员看到的字符编码。在Unicode中我们有很多方式将数字23383表示成程序中的数据包括 UTF-8、 UTF-16、 UTF-32。 UTF是"UCSTransformation Format"的缩写可以翻译成Unicode字符集转换格式 即怎样将Unicode定义的数字转换成程序数据。例如 "汉字"对应的数字是0x6c49和0x5b57而编码的程序数据是
BYTE data_utf8={0xE6,0xB1,0x89,0xE5,0xAD,0x97} //UTF-8编码WORDdata_utf 16={0x6c49,0x5b57} //UTF-16编码DWORDdata_utf32={0x6c49,0x5b57} //UTF-32编码这里用BYTE、 WORD、 DWORD分别表示无符号8位整数无符号16位整数和无符号32位整数。 UTF-8、 UTF-16、UTF-32分别以BYTE、 WORD、 DWORD作为编码单位。
"汉字"的UTF-8编码需要6个字节。 "汉字"的UTF-16编码需要两个WORD大小是4个字节。 "汉字"的UTF-32编码需要两个DWORD大小是8个字节。
4.2节会介绍将数字映射到UTF编码的规则。
3.4作为字节流的字符(Character encoding scheme)
字符编码的第四层是计算机看到的字符 即在文件或内存中的字节流。例如 "字"的UTF-32编码是0x5b57如果用little endian表示字节流是"575b 00 00"。如果用big endian表示字节流是"00 00 5b 57"。
字符编码的第三层规定了一个字符由哪些编码单位按什么顺序表示。字符编码的第四层在第三层的基础上又考虑了编码单位内部的字节序。 UTF-8的编码单位是字节不受字节序的影响。 UTF-16、 UTF-32根据字节序的不同又衍生出UTF-16LE、 UTF-16BE、 UTF-32LE、 UTF-32BE四种编码方案。 LE和BE分别是Little Endian和Big Endian的缩写。
3.5小结
通过四层模型我们又把字符编码中发生的这些事情梳理了一遍。其实大多数代码页都不需要完整的四层模型例如GB 18030以字节为编码单位直接规定了字节序列和字符的映射关系跳过了第二层也不需要第四层。
4再谈Unicode Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。 Unicode用数字0-0x10FFFF来映射这些字符最多可以容纳个字符或者说有个码位。码位就是可以分配给字符的数字。 UTF-8、 UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
Unicode字符集可以简写为UCS(Unicode Character Set) 。早期的Unicode标准有UCS-2、 UCS-4的说法。 UCS-2用两个字节编码 UCS-4用4个字节编码。 UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane) 。每个平面根据第3个字节分为256行(row) 每行有256个码位(cell) 。 group 0的平面0被称作BMP(BasicMultilingual Plane) 。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。
Unicode标准计划使用group 0的17个平面从BMP(平面0)到平面16即数字0-0x10FFFF。 《谈谈Unicode编码》主要介绍了BMP的编码本文将介绍完整的Unicode编码并从多个角度浏览Unicode。本文的介绍基于Unicode
5.0.0版本。
4. 1浏览Unicode
先看一些数字每个平面有2^16=65536个码位。 Unicode计划使用了17个平面一共有17*65536=1114112个码位。其实现在已定义的码位只有
今年1月的时候Hosteons开始提供1Gbps端口KVM架构VPS,目前商家在LET发布消息,到本月30日之前,用户下单洛杉矶/纽约/达拉斯三个地区机房KVM主机可以从1Gbps免费升级到10Gbps端口,最低年付仅21美元起。Hosteons是一家成立于2018年的国外VPS主机商,主要提供VPS、Hybrid Dedicated Servers及独立服务器租用等,提供IPv4+IPv6,支持...
国外主机测评昨天接到Hostigger(现Hostiger)商家邮件推送,称其又推出了一款特价大内存VPS,机房位于土耳其的亚欧交界城市伊斯坦布尔,核50G SSD硬盘200Mbps带宽不限月流量只要$59/年。 最近一次分享的促销信息还是5月底,当时商家推出的是同机房同配置的大内存VPS,价格是$59.99/年,不过内存只有10G,虽然同样是大内存,但想必这次商家给出16G,价格却是$59/年,...
atcloud怎么样?atcloud刚刚发布了最新的8折优惠码,该商家主要提供常规cloud(VPS)和storage(大硬盘存储)系列VPS,其数据中心分布在美国(俄勒冈、弗吉尼亚)、加拿大、英国、法国、德国、新加坡,所有VPS默认提供480Gbps的超高DDoS防御。Atcloud高防VPS。atcloud.net,2020年成立,主要提供基于KVM虚拟架构的VPS、只能DNS解析、域名、SS...