每一个程序员都绝对、积极地需要了解编码和字符集来处理文本
如果你在计算机中处理文本,你需要了解编码。时期是的,即使你只是发送电子邮件。即使你只是收到电子邮件。你没有';我不需要理解每一个细节,但你至少必须知道这整个";编码";事情是这样的。首先,好消息是:虽然这个话题可能会变得混乱,令人困惑,但基本思路非常非常简单。
本文是关于编码和字符集的。Joel Spolsky的一篇文章,题为《绝对最小值——每个软件开发人员绝对、肯定必须了解Unicode和字符集》(没有借口!)这是一个很好的主题介绍,我非常喜欢偶尔读一读。我不太愿意让那些在理解编码问题上有困难的人使用它,尽管这很有趣,但它对实际的技术细节却非常浅显。我希望这篇文章能进一步阐明编码到底是什么,以及为什么在你最不需要的时候,你的所有文本都会出错。本文的目标是开发人员(重点是PHP),但任何计算机用户都应该能够从中受益。
在某种程度上,每个人都知道这一点,但不知何故,这种知识似乎突然消失在关于文本的讨论中,所以让';让我们先把它拿出来:计算机不能存储";字母""数字""图片";或者别的什么。它唯一能存储和使用的东西是比特。一个位只能有两个值:是或否、真或假、1或0或任何你想称之为这两个值的值。因为计算机是用电工作的,所以";实际和#34;bit是电的一点,要么是,要么不是';不在那里。对于人类来说,这通常用1和0以及I'来表示;在这篇文章中,我们将坚持这个惯例。
要用位来表示除位之外的任何东西,我们需要规则。我们需要使用一种编码方案,或简称编码,将一系列位转换成字母、数字和图片。这样地:
在这种编码中,01100010代表字母#34;b";,01101001为字母";我";,01110100代表";t";和011110011用于";s";。一个特定的位序列代表一个字母,一个字母代表一个特定的位序列。如果你能把它记在脑子里26个字母,或者在桌子上快速查找东西,你可以像读一本书一样读一些东西。
上述编码方案恰好是ASCII码。由1和0组成的字符串被分解成8位的部分(简称一个字节)。ASCII编码指定将字节转换为人类可读字母的表格。这里';下面是该表格的一个简短摘录:
ASCII表中指定了95个人类可读字符,包括大写和小写字母A到Z、数字0到9、少量标点符号和字符,如美元符号、符号和其他一些字符。它还包括空间、换行、制表符、退格等33个值。它们本身不可打印,但仍以某种形式可见,对人类直接有用。许多值只对计算机有用,比如表示文本开始或结束的代码。ASCII编码总共定义了128个字符,这是一个很好的整数(对于与计算机打交道的人来说),因为它使用了7位的所有可能组合(0000000、0000001、0000010到1111111)。1.
现在你有了它,一种只使用1和0来表示人类可读文本的方法。
要用ASCII编码,请按照表格从右到左的顺序,用字母代替位。要将一串位解码为人类可读的字符,请按照表格从左到右的顺序,用位替换字母。
编码| enˈkōd |动词[用obj.]转换成编码形式
代码| kōd |名词一个由单词、字母、数字或其他符号组成的系统,用来代替其他单词、字母等。
编码意味着用某物来表示其他的东西。编码是一组规则,用于将某个事物从一种表示形式转换为另一种表示形式。
可以编码的字符集"ASCII编码包含128个字符的字符集" 本质上与"同义;编码";。
A";第34页;将字符映射到数字或位序列的代码。A.k.A.和#34;桌子";。本质上与"同义;编码";。
一根绳子是一堆串在一起的东西。位字符串是一组位,比如01010011。字符串是一组字符,就像这样。与#34同义;顺序";。
写数字有很多方法。10011111在二进制中是237在八进制中是159在十进制中是9F在十六进制中。它们都代表相同的值,但十六进制比二进制更短,更容易读取。在本文中,我将坚持使用二进制,以便更好地理解这一点,并为读者节省一层抽象。如果在其他地方看到其他符号中提到的字符代码,请不要惊慌,因为它';都一样。
现在我们知道我们';我们在谈论,让';就这么说吧:95个字符真的不是';说到语言,我觉得不多。它涵盖了英语的基础知识,但是用法语写一封情书怎么样?德语中的Straßenübergangsänderungsägesetz?瑞典语的一份邀请信?嗯,你不能';t、 不是ASCII码。那里';没有关于如何在ASCII中表示字母é、ß、ü、ä、ö或å的规范,因此您可以';不要用它们。
"但看看它,";欧洲人说";在一台字节为8位的普通计算机中,ASCII浪费了一整位,而这一整位总是设置为0!我们可以用这个钻头挤压一个整体#39;表中还有128个值" 他们就是这样做的。但即便如此,仍然有超过128种方法可以划过、切掉、斜线和点元音。并非所有欧洲语言中使用的字母和波形的所有变体都可以在同一个表中以最多256个值表示。因此,世界最终得到了大量的编码方案、标准、事实上的标准和半标准,它们都涵盖了不同的字符子集。有人需要用捷克语写一份关于瑞典语的文件,发现没有编码覆盖两种语言,于是发明了一种。或者说,我想这已经发生了无数次了。
不要忘了俄语、印地语、阿拉伯语、希伯来语、韩语和所有其他目前在这个星球上活跃使用的语言。更不用说那些不再使用的了。一旦你解决了如何用所有这些语言编写混合语言文档的问题,那就试试中文吧。或者日本人。两者都包含数万个字符。一个由8位组成的字节有256个可能的值。去
要为使用256个字符以上的语言创建一个将字符映射为字母的表,一个字节根本不是';这还不够。使用两个字节(16位),它';可以对65536个不同的值进行编码。BIG-5就是这样一种双字节编码。它不是将一串位分解为八个块,而是将其分解为16个块,并有一个大的(我的意思是,大的)表,指定每个位组合映射到哪个字符。基本形式的BIG-5主要包括繁体汉字。GB18030是另一种基本上做相同事情的编码,但包括繁体和简体汉字。在你问之前,是的,有一些编码只包括简体中文。可以';我们现在不能只有一种编码,对吗?
GB18030涵盖了相当多的字符(包括大部分拉丁字符),但最终是许多字符中的另一种特殊编码格式。
最后,有人受够了这种混乱,开始锻造一个戒指,把它们都绑起来,创建一个编码标准来统一所有的编码标准。这个标准是Unicode。它基本上定义了一个包含1114112个代码点的ginormous表,可以用于各种字母和符号。那';这足以编码所有欧洲、中东、远东、南部、北部、西部、史前和人类所知的未来人物。2使用Unicode,你可以使用任何可以在计算机中键入的字符编写一个包含几乎任何语言的文档。在Unicode出现之前,这要么是不可能的,要么是非常困难的。那里';这甚至是Unicode中克林贡语的非官方部分。事实上,Unicode足够大,允许非官方的私人使用区域。
那么,Unicode使用多少位来编码所有这些字符呢?没有一个因为Unicode不是一种编码。
困惑的许多人似乎是这样。Unicode首先定义了字符的代码点表。那';这是一种奇特的说法";65代表A,66代表B,9731代表☃" (说真的,的确如此)。这些代码点实际上是如何被编码成比特的是另一个话题。为了表示1114112个不同的值,两个字节是';这还不够。三个字节是,但三个字节通常很难使用,所以四个字节是最合适的最小值。但是,除非你';如果你真的使用中文或其他一些大数字字符,需要很多位来编码,你';我们永远不会使用这四个字节中的一大块。如果字母";A";始终被编码为00000000000000000001000001和#34;B";始终为00000000000000000000000001000010等等,任何文档都会膨胀到所需大小的四倍。
为了优化这一点,有几种方法可以将Unicode代码点编码为位。UTF-32是一种使用32位编码所有Unicode码点的编码。也就是说,每个字符有四个字节。它';这很简单,但通常会浪费很多空间。UTF-16和UTF-8是可变长度编码。如果一个字符可以用一个字节来表示(因为它的代码点是一个非常小的数字),UTF-8将用一个字节对其进行编码。如果需要两个字节,它将使用两个字节,以此类推。它有详细的方法来使用字节中的最高位来表示一个字符由多少字节组成。这可以节省空间,但如果需要经常使用这些信号位,也可能会浪费空间。UTF 16处于中间,使用至少两个字节,根据需要增长到四字节。
那';这就是全部。Unicode是一个将字符映射到数字的大表,不同的UTF编码指定这些数字如何编码为位。总的来说,Unicode是另一种编码方案。那里';这没什么特别的,它';It’他只是想在保持效率的同时覆盖一切。那';这是件好事。™
字符由其"表示;Unicode代码点";。Unicode代码点是用十六进制(以保持数字更短)编写的,前面是";U+#34;(这正是他们所做的,除了";这是一个Unicode码点";)没有其他意义。角色Ḁ 具有Unicode代码点U+1E00。换句话说(十进制),它是Unicode表的第7680个字符。官方名称为";拉丁文大写字母A,下面带圈";。
以上所有内容的总结:任何字符都可以在许多不同的位序列中编码,任何特定的位序列都可以代表许多不同的字符,这取决于读取或写入字符所使用的编码。原因很简单,因为不同的编码每个字符使用不同的比特数和不同的值来表示不同的字符。
说到这里,我们来谈谈许多用户和程序员每天遇到的实际问题,这些问题与上述所有问题的关系,以及他们的解决方案。最大的问题是:
如果你打开一个文档,它看起来像这样,那么';这只有一个原因:你的文本编辑器、浏览器、文字处理器或其他任何东西';s试图读取文档时假设编码错误。那';就这些。文件没有破损(好吧,除非是破损的,见下文),有';It’您不需要执行任何魔术,只需选择正确的编码即可显示文档。
10000011 01000111 10000011 10010011 10000011 01010010 10000001 0101101110000011 01100110 10000011 01000010 10000011 10010011 10000011 0100111110000010 11001101 10010011 11101111 10000010 10110101 10000010 1010110110000010 11001000 10000010 10100010
快,那是什么编码?如果你只是耸耸肩,你';那是对的。谁知道,对吧
好吧,让';让我们试着将其解释为ASCII。嗯,大多数字节都是以1位开始的。如果你没记错的话,ASCII不会';别用那个。所以它';这不是ASCII码。UTF-8呢?不,大多数序列都不是有效的UTF-8。所以UTF-8也被淘汰了。让';让我们试试";麦克·罗曼";(对他们来说,这是另一种编码方案)。嘿,所有这些字节在Mac Roman中都是有效的。1000011映射到";É", 01000111至";G";等等如果使用Mac Roman编码读取该位序列,结果为";ÉGìÉRÉ≠ǻǢ". 看起来是个有效的字符串,不是吗?对大概那么,如何';计算机知道什么?也许有人想写";ÉGìÉRÉ≠ǻǢ". 据我所知,这可能是一个DNA序列。5除非你有更好的建议,否则就让';让我们宣布这是一个DNA序列,说这个文件是用Mac Roman编码的,到此为止。
当然,不幸的是,这完全是胡说八道。正确的答案是,这篇文章是用日文Shift-JIS编码的,应该读";エンコーディングは難しくない". 嗯,谁';d';维特·图恩?
造成文本乱码的主要原因是:有人试图用错误的编码读取字节序列。计算机总是需要被告知某些文本的编码方式。否则它可以';我不知道。不同类型的文档可以通过不同的方式指定它们的编码';我们应该使用这些方法。原始位序列总是一个神秘的盒子,可能意味着任何东西。
大多数浏览器允许在菜单选项";文本编码";,这会导致浏览器使用选定的编码重新解释当前页面。其他项目可能会提供";使用编码重新打开…";在文件菜单中,或者可能是";进口…#34;允许用户手动选择编码的选项。
如果一个位序列没有';(对人类)来说,任何编码都没有意义,文档很可能在某个时候被错误地转换了。假设我们把上面的文字";ÉGìÉRÉ≠ǻǢ" 因为我们没有';I don’我不知道更多,并将其保存为UTF-8。文本编辑器假定它正确地读取了Mac Roman编码的文本,现在您希望以不同的编码保存该文本。毕竟,所有这些字符都是有效的Unicode字符。也就是说,有';s Unicode中的一个代码点,可以表示";É", 一个能代表";G";等等因此,我们可以很高兴地将此文本保存为UTF-8:
11000011 10001001 01000111 11000011 10001001 11000011 10101100 1100001110001001 01010010 11000011 10000101 01011011 11000011 10001001 0110011011000011 10001001 01000010 11000011 10001001 11000011 10101100 1100001110001001 01001111 11000011 10000111 11000011 10010101 11000011 1010110011000011 10010100 11000011 10000111 11000010 10110101 11000011 1000011111100010 10001001 10100000 11000011 10000111 11000010 10111011 1100001110000111 11000010 10100010
这是表示文本的UTF-8位序列";ÉGìÉRÉ≠ǻǢ". 这个位序列与我们的原始文档完全无关。不管我们试图用什么编码来打开它,我们都赢了';我从来没有读过这篇文章";エンコーディングは難しくない" 从中。它完全消失了。如果我们知道Shift JIS文档被错误地理解为Mac Roman,然后意外地保存为UTF-8,并扭转了这一错误链,那么就有可能从中恢复原始文本。但这将是一个幸运的运气。
很多时候,某些位序列在特定编码中是无效的。如果我们尝试使用ASCII打开原始文档,一些字节在ASCII中是有效的,并映射到真实字符,而其他字节则不会';t、 你';使用重新打开时,可能会决定以静默方式丢弃任何不为';t在所选的编码中无效,或者可能替换为?。那里';这也是";Unicode替换字符";� (U+FFFD)程序可能决定为其无法插入的任何字符插入';尝试处理Unicode时无法正确解码。如果文档保存时某些字符消失或被替换,那么这些字符实际上就永远消失了,无法对它们进行反向工程。
如果文档被误解并转换为不同的编码,则它';它坏了。试图";修理和#34;它可能成功,也可能不成功,通常不是';t、 任何手动位移动或其他编码巫毒都是巫毒。它';It’在病人去世后,他正在努力纠正症状。
它';这真的很简单:知道某段文本的编码是什么,也就是某个字节序列,然后用这种编码来解释它。那';这就是你需要做的。如果你';重新编写一个允许用户输入一些文本的应用程序,指定您从用户那里接受的编码。对于任何类型的文本字段,程序员通常可以决定其编码。对于用户可能上传或导入到程序中的任何类型的文件,都需要有一个规范,说明该文件应该采用什么编码。或者,用户需要某种方法来告诉程序文件的编码是什么。这些信息可能是文件格式本身的一部分,也可能是用户做出的选择(大多数用户通常不会知道,除非他们读过本文)。
如果您需要从一种编码转换为另一种编码,请使用专门用于转换的工具干净地进行转换。在编码之间转换是一项繁琐的任务,比较两个代码页,确定编码A中的字符152与编码B中的字符4122相同,然后相应地更改位。这个特殊的轮子不需要重新发明,任何主流编程语言都包括一些将文本从一种编码转换为另一种编码的方法,而不需要考虑代码点、页面或位。
比如说,你的应用程序必须接受GB18030中上传的文件,但你在内部处理的是UTF-32中的所有数据。像iconv这样的工具可以使用像iconv这样的一行代码(GB18030';,';UTF-32';,$string)干净地转换上传的文件。也就是说,它将在更改基础位的同时保留字符:
那';这就是全部。字符串的内容,即人类可读的字符,没有';不会改变,但它';现在是一个有效的UTF-32字符串。如果你一直把它当作UTF-32,那么';It’乱码没问题。不过,正如一开始所讨论的,并非所有的编码方案都能代表所有字符。它';不可能对字符进行编码#34;縧" 在任何为欧洲语言设计的编码方案中。糟糕的事™ 如果你尝试的话就会发生。
正因为如此,才有';在这个时代,几乎没有理由不一直使用Unicode。对于某些语言,某些专用编码可能比Unicode编码更有效。但除非你';重新存储太字节和太字节的非常专门的文本(这是很多文本),有';it’通常没有理由担心。如今,由不兼容的编码方案引起的问题比浪费一两个千兆字节要严重得多。随着存储和带宽的不断增大和降低,这一点将变得更加真实。
如果你的系统
......