学点Unicode又不会死——Unicode的流言终结者和编码大揭秘

2024-08-24 01:58

本文主要是介绍学点Unicode又不会死——Unicode的流言终结者和编码大揭秘,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


学点Unicode又不会死——Unicode的流言终结者和编码大揭秘

摘要: 如果你是一个生活在2003年的程序员,却不了解字符、字符集、编码和Unicode这些基础知识。那你可要小心了,要是被我抓到你,我会让你在潜水艇里剥六个月洋葱来惩罚你。这个邪恶的恐吓是Joel Spolsky在十年前首次发出 ...

如果你是一个生活在2003年的程序员,却不了解字符、字符集、编码和Unicode这些基础知识。那你可要小心了,要是被我抓到你,我会让你在潜水艇里剥六个月洋葱来惩罚你。

这个邪恶的恐吓是Joel Spolsky在十年前首次发出的。不幸的是,很多人认为他只是在开玩笑,因此,现在仍有许多人不能完全理解Unicode,以及Unicode、UTF-8、UTF-16之间的区别。这就是我写这篇文章的原因。

言归正传,设想在一个晴朗的下午,你收到一封电子邮件,它来自一个你高中之后就失去联系的朋友,并带有一个txt格式(也称为纯文本格式)的附件。这个附件包含下面这样一串二进制bits:

0100100001000101010011000100110001001111

Email的正文是空的,这使它更加神秘。在你启动常用的文本编辑器打开这个附件之前,你有没有想过,文本编辑器是怎么将二进制形式翻译成字符的?这其中有两个关键问题:

  1. 字节是怎样分组的?(例如1个字节的字符和2个字节的字符)

  2. 一个或多个字节是怎么映射到字符上的?

这些问题的答案就在这篇文档(Character Encoding)中,大致说来,编码定义了两件事:

  1. 字节是怎么分组的,如8 bits或16 bits一组,这也被称作编码单元。

  2. 编码单元和字符之间的映射关系。例如,在ASCII码中,十进制65映射到字母A上

字符编码和字符集之间有微小的区别。不过通常它和你无关,除非你在设计一个底层的库。

ASCII码是上个世纪最流行的编码体系之一,至少在西方是这样。下图显示了ASCII码中编码单元是怎么映射到字符上的。

asciifull

有一个即使在经验丰富的程序员中也非常常见的误解就是,纯文本使用ASCII码并且每个字符都是8 bits。

事实是,没有这样的「纯文本」。如果在内存或者硬盘中有一个你不知道编码的字符串,那你就无法翻译或者显示它。这绝对没有第二条路可选。

那么当你刚刚收到的附件没有指定编码格式的时候,计算机会如何翻译它呢?这是否意味着你就永远也读不到失去联系的老朋友想跟你说的话了呢?在我们找到答案之前,我们首先回到那个年代————那个用钱能买到的最大硬盘是29MB的时代。

历史回顾

很久以前,计算机制造商有自己的表示字符的方式。他们并不需要担心如何和其它计算机交流,并提出了各自的方式来将字形渲染到屏幕上。随着计算机越来越流行,厂商之间的竞争更加激烈,在不同的计算机体系间转换数据变得十分蛋疼,人们厌烦了这种自定义造成的混乱。

最终,计算机制造商一起制定了一个标准的方法来描述字符。他们定义使用一个字节的低7位来表示字符,并且制作了如上图所示的对照表来映射七个比特的值到一个字符上。例如,字母A是65,c是99,~是126等等, ASCII码就这样诞生了。原始的ASCII标准定义了从0到127 的字符,这样正好能用七个比特表示。不过好景不长……

为什么选择了7个比特而不是8个来表示一个字符呢?我并不关心。但是一个字节是8个比特,这意味着1个比特并没有被使用,也就是从128到255的编码并没有被制定ASCII标准的人所规定,这些美国人对世界的其它地方一无所知甚至完全不关心。

其它国家的人趁这个机会开始使用128到255范围内的编码来表达自己语言中的字符。例如,144在阿拉伯人的ASCII码中是گ,而在俄罗斯的ASCII码中是ђ。即使在美国,对于未使用区域也有各种各样的利用。IBM PC就出现了“OEM 字体”或”扩展ASCII码”,为用户提供漂亮的图形文字来绘制文本框并支持一些欧洲字符,例如英镑(£)符号。

lpcec9qu3zw3u5uldh_bnawejbesszsmhiv2jm-pyemhgj2ky0vkvn0ousiaqvmv4kmsg_gmhhlhwcbem-uem4wcxkm5hcungr3r7bibhm1ievimyks2cxidbg

用IBM扩展字符集绘制的很酷的DOS启动画面

再强调一遍,ASCII码的问题在于尽管所有人都在0-127号字符的使用上达成了一致,但对于128-255号字符却有很多很多不同的解释。你必须告诉计算机使用哪种风格的ASCII码才能正确显示128-255号的字符。

这对于北美人和不列颠群岛的人来说不算什么问题,因为无论使用哪种风格的ASCII码,拉丁字母的显示都是一样的。英国人还需要面对的问题是原始的ASCII码中不包含英镑符号,但是这个已经无关紧要了。

与此同时,在亚洲有更让人头疼的问题。亚洲语言有更多的字符和字形需要被存储,一个字节已经不够用了。所以他们开始使用两个字节来存储字符,这被称作DBCS(双字节编码方案)。在DBCS中,字符串操作变得很蛋疼,你应该怎么做str++或str–?

这些问题成为了系统开发者的噩梦。例如,MS DOS必须支持所有风格的ASCII码,因为他们想把软件卖到其他国家去。他们提出了「内码表」这一概念。例如,你需要告诉DOS(通过使用”chcp”命令)你想使用保加利亚语的内码表,它才能显示保加利亚字母。内码表的更换会应用到整个系统。这对使用多种语言工作的人来说是一个问题,因为他们必须频繁的在几个内码表之间来回切换。

尽管内码表是一个好主意,但是它不是一个简洁的解决方案,它只是一个hack技术或者说是简单的修正来让编码系统可以工作。

进入Unicode的世界

最终,美国人意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,以便缓解程序员的痛苦和避免字符编码引发的第三次世界大战。出于这个目的,Unicode诞生了。

Unicode背后的想法非常简单,然而却被普遍的误解了。Unicode就像一个电话本,标记着字符和数字之间的映射关系。Joel称之为「神奇数字」,因为它们可能是随机指定的,而且不会给出任何解释。官方术语是码位(Code Point),总是用U+开头。理论上每种语言中的每种字符都被Unicode协会指定了一个神奇数字。例如希伯来文中的第一个字母א,是U+2135,字母A是U+0061。

Unicode并不涉及字符是怎么在字节中表示的,它仅仅指定了字符对应的数字,仅此而已。

关于Unicode的其它误解包括:Unicode支持的字符上限是65536个,Unicode字符必须占两个字节。告诉你这些的人应该去换换脑子了。

记住,Unicode只是一个用来映射字符和数字的标准。它对支持字符的数量没有限制,也不要求字符必须占两个、三个或者其它任意数量的字节。

Unicode字符是怎样被编码成内存中的字节这是另外的话题,它是被UTF(Unicode Transformation Formats)定义的。

Unicode编码

两个最流行的Unicode编码方案是UTF-8和UTF-16。让我们看看它们的细节。

UTF-8

UTF-8是一个非常惊艳的概念,它漂亮的实现了对ASCII码的向后兼容,以保证Unicode可以被大众接受。发明它的人至少应该得个诺贝尔和平奖。

在UTF-8中,0-127号的字符用1个字节来表示,使用和US-ASCII相同的编码。这意味着1980年代写的文档用UTF-8打开一点问题都没有。只有128号及以上的字符才用2个,3个或者4个字节来表示。因此,UTF-8被称作可变长度编码。

回到文章开始的问题,来自你老朋友的附件的字节流如下:

0100100001000101010011000100110001001111

这个字节流在ASCII和UTF-8中表示相同的字符:HELLO

UTF-16

另一个流行的可变长度编码方案是UTF-16,它使用2个或者4个字节来存储字符。然而,人们逐渐意识到UTF-16可能会浪费存储空间,但那是另一个话题了。

低字节序(Little Endian)和高字节序(Big Endian)

Endian读作End-ian或者Indian。这个术语的起源可以追溯到格列佛游记。(小说中,小人国为水煮蛋应该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派”和“小端派”。)

低字节序和高字节序只是一个关于在内存中存储和读取一段字节(被称作words)的约定。这意味着当你让计算机用UTF-16把字母A(占两个字节)存在内存中时,使用哪种字节序方案决定了你把第一个字节放在第二个字节的前面还是后面。这么说有点不太容易懂,让我们来看一个例子:当你使用UTF-16存下来自你朋友的附件时,在不同的系统中它的后半部分可能是这样的:

00 68 00 65 00 6C 00 6C 00 6F(高字节序,高位字节被存在前面)

68 00 65 00 6C 00 6C 00 6F 00(低字节序,低位字节被存在前面)

字节序方案只是一个微处理器架构设计者的偏好问题,例如,Intel使用低字节序,Motorola使用高字节序。

字节顺序标记(BOM)

如果你经常要在高低字节序的系统间转换文档,并且希望区分字节序,还有一种奇怪的约定,被称作BOM。BOM是一个设计得很巧妙的字符,用来放在文档的开头告诉阅读器该文档的字节序。在UTF-16中,它是通过在第一个字节放置FE FF来实现的。在不同字节序的文档中,它会被显示成FF FE或者FE FF,清楚的把这篇文档的字节序告诉了解释器。

BOM尽管很有用,但并不是很简洁,因为还有一个类似的概念,称作「魔术字」(Magic Byte),很多年来一直被用来表明文件的格式。BOM和魔术字间的关系一直没有被清楚的定义过,因此有的解释器会搞混它们。

恭喜你读到这里,你一定是一个很有耐心的读者。

还记得文章开头的问题吗,既然没有「纯文本」文件这回事,那你的文本编辑器和浏览器为什么每次都能正确的显示内容呢?答案是,那些软件欺骗了你,这也是为什么那么多人对编码一无所知。当软件不能确定编码的时候,它会猜测。大部分时候,它会猜测是否是涵盖了ASCII码的UTF-8,还是ISO-8859-1,也有可能猜其他能想到的任意字符集。因为英文中使用的拉丁字母表在几乎所有的字符集中都能显示,包括UTF-8,所以即使编码猜错了,英文字母看起来也是正确的。

但是,如果你在浏览网页时看到�符号,这意味着这个网页的编码不是你的浏览器猜测的那个。这时你可以点开浏览器的查看——>字符编码菜单来尝试不同的编码。

总结

如果你没时间读整篇文章或者你仅仅是略读了一下前面的内容。那请你确保你能理解下面的几条:

  • 这个世界上从来没有纯文本这回事,如果你想读出一个字符串,你必须知道它的编码。

  • Unicode是一个简单的标准,用来把字符映射到数字上。Unicode协会的人会帮你处理所有幕后的问题,包括为新字符指定编码。

  • Unicode并不告诉你字符是怎么编码成字节的。这是被编码方案决定的,通过UTF来指定。

还有最重要的:

  • 永远记得通过Content-Type或者meta charset标签来显式指定你的文档的编码。这样浏览器就不需要猜测你使用的编码了,他们会准确的使用你指定的编码来渲染文档。


来源声明:本文来自于10K-LOC的博文《Unicode isn’t harmful for health – Unicode Myths debunked and encodings demystified》,由IDF实验室封畅翻译。


本文地址:http://linux.cn/article-2572-1.html

这篇关于学点Unicode又不会死——Unicode的流言终结者和编码大揭秘的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1101125

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

揭秘世界上那些同时横跨两大洲的国家

我们在《世界人口过亿的一级行政区分布》盘点全球是那些人口过亿的一级行政区。 现在我们介绍五个横跨两州的国家,并整理七大洲和这些国家的KML矢量数据分析分享给大家,如果你需要这些数据,请在文末查看领取方式。 世界上横跨两大洲的国家 地球被分为七个大洲分别是亚洲、欧洲、北美洲、南美洲、非洲、大洋洲和南极洲。 七大洲示意图 其中,南极洲是无人居住的大陆,而其他六个大洲则孕育了众多国家和

三国地理揭秘:为何北伐之路如此艰难,为何诸葛亮无法攻克陇右小城?

俗话说:天时不如地利,不是随便说说,诸葛亮六出祁山,连关中陇右的几座小城都攻不下来,行军山高路险,无法携带和建造攻城器械,是最难的,所以在汉中,无论从哪一方进攻,防守方都是一夫当关,万夫莫开;再加上千里运粮,根本不需要打,司马懿只需要坚守城池拼消耗就能不战而屈人之兵。 另一边,洛阳的虎牢关,一旦突破,洛阳就无险可守,这样的进军路线,才是顺势而为的用兵之道。 读历史的时候我们常常看到某一方势

无线领夹麦克风什么牌子好用?揭秘领夹麦克风哪个牌子音质好!

随着短视频行业的星期,围绕着直播和视频拍摄的电子数码类产品也迎来了热销不减的高增长,其中除了数码相机外,最为重要的麦克风也得到了日益增长的高需求,尤其是无线领夹麦克风,近几年可谓是异常火爆。别看小小的一对无线麦克风,它对于视频拍摄的音质起到了极为关键的作用。 不过目前市面上的麦克风品牌种类多到让人眼花缭乱,盲目挑选的话容易踩雷,那么无线领夹麦克风什么牌子好用?今天就给大家推荐几款音质好的

负债不再是障碍?银行信贷“白名单“揭秘

谈及银行信贷产品,常闻有言称存在无需考量负债与查询记录之奇品,此等说法十有八九为中介诱人上钩之辞。轻信之下,恐将步入连环陷阱。除非个人资质出类拔萃,如就职于国央企或事业单位,工龄逾年,五险一金完备,还款能力卓越,或能偶遇线下产品对查询记录稍显宽容,然亦非全然无视。宣称全然不顾者,纯属无稽之谈。 银行非慈善机构,不轻易于困境中援手,更偏爱锦上添花之举。若无坚实资质,即便求助于银行亦难获青睐。反

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

form表单提交编码的问题

浏览器在form提交后,会生成一个HTTP的头部信息"content-type",标准规定其形式为Content-type: application/x-www-form-urlencoded; charset=UTF-8        那么我们如果需要修改编码,不使用默认的,那么可以如下这样操作修改编码,来满足需求: hmtl代码:   <meta http-equiv="Conte

如何保证android程序进程不到万不得已的情况下,不会被结束

最近,做一个调用系统自带相机的那么一个功能,遇到的坑,在此记录一下。 设备:红米note4 问题起因 因为自定义的相机,很难满足客户的所有需要,比如:自拍杆的支持,优化方面等等。这些方面自定义的相机都不比系统自带的好,因为有些系统都是商家定制的,难免会出现一个奇葩的问题。比如:你在这款手机上运行,无任何问题,然而你换一款手机后,问题就出现了。 比如:小米的红米系列,你启用系统自带拍照功能后

4-4.Andorid Camera 之简化编码模板(获取摄像头 ID、选择最优预览尺寸)

一、Camera 简化思路 在 Camera 的开发中,其实我们通常只关注打开相机、图像预览和关闭相机,其他的步骤我们不应该花费太多的精力 为此,应该提供一个工具类,它有处理相机的一些基本工具方法,包括获取摄像头 ID、选择最优预览尺寸以及打印相机参数信息 二、Camera 工具类 CameraIdResult.java public class CameraIdResult {