每个程序员在处理文本时绝对、肯定需要了解的关于编码和字符集的内容。
如果您在计算机中处理文本,则需要了解编码。句号。是的,即使你只是在发电子邮件。即使你只是在接收电子邮件。你不需要了解每一个细节,但你至少必须知道整个编码是关于什么的。首先是好消息:虽然这个话题可能会变得杂乱无章,但基本思想真的非常简单。
本文是关于编码和字符集的。Joel Spolsky的一篇题为“每个软件开发人员绝对、肯定地必须了解Unicode和字符集的绝对最低要求”的文章(没有借口!)。是对这个主题的一个很好的介绍,我很喜欢偶尔读一读。我不愿让那些理解编码问题有困难的人参考它,因为在娱乐的同时,它对实际的技术细节相当少。我希望这篇文章能更好地解释编码到底是什么,以及为什么所有的文本在您最不需要的时候都搞砸了。本文面向开发人员(重点关注PHP),但任何计算机用户都应该能够从中受益。
每个人都在某种程度上意识到了这一点,但不知何故,在一次关于文本的讨论中,这一知识似乎突然消失了,所以让我们先把它说出来:计算机不能存储字母、数字、图片或其他任何东西。它唯一可以存储和处理的东西就是比特。一位只能有两个值:是或否、真或假、1或0或您想称这两个值为其他任何值。因为计算机是与电一起工作的,所以实际的比特是不在那里或不在那里的一瞬间的电。对于人类来说,这通常使用1和0来表示,我将在本文中始终遵循这一约定。
要使用位来表示位之外的任何东西,我们需要规则。我们需要使用编码方案或简称编码将位序列转换为字母、数字和图片之类的东西。就像这样:
在这种编码中,01100010代表字母&34;b&34;,01101001代表字母&34;i&34;,01110100代表";t&34;,01110011代表";s&34;。某一位序列代表一个字母,一个字母代表某一位序列。如果你能把这句话记住26个字母,或者在表格中快速查找,你就可以像读一本书一样阅读一些东西。
上述编码方案恰好是ASCII。由1和0组成的字符串被分解成每个8位的部分(缩写为一个字节)。ASCII编码指定将字节转换为人类可读字母的表。以下是该表格简短摘录:
ASCII表中指定了95个人类可读字符,包括大小写字母A到Z、数字0到9、一些标点符号以及美元符号、与号等字符。它还包括空格、换行符、制表符、退格键等33个值。这些文件本身不可打印,但仍以某种形式可见,并直接对人类有用。许多值只对计算机有用,比如表示文本开始或结束的代码。ASCII码总共定义了128个字符,这是一个不错的整数(对于与计算机打交道的人来说),因为它使用7位(0000000、0000001、0000010到1111111)的所有可能组合。1个
现在您就知道了,只使用1和0来表示人类可读文本的方法。
要用ASCII编码,请按照表中从右到左的顺序,用字母代替位。要将位字符串解码为人类可读的字符,请按照表中从左到右的顺序进行操作,用位代替字母。
Encode|enˈkōd|动词[带Obj.。]。转换为编码形式。
Code|kōd|名词用单词、字母、数字或其他符号代替其他单词、字母等的系统。
Encode的意思是用某种东西来代表另一种东西。编码是用于将某物从一种表示转换为另一种表示的一组规则。
可以编码的字符集。";ASCII编码包含128个字符的字符集。";实质上等同于";编码";。
将字符映射到数字或位序列的代码的第";页。又名。桌子。本质上是编码的同义词。
字符串是一串串在一起的物品。位串是一串位,比如01010011。字符串是一串字符,就像这样。同义词";序列";。
写数字的方法有很多种。二进制是10011111,八进制是237,十进制是159,十六进制是9F。它们都表示相同的值,但是十六进制比二进制更短且更容易读取。我将在本文中始终使用二进制,以便更好地理解这一点,并为读者省去一层抽象层。不要因为在其他地方看到其他符号中提到的字符代码而感到惊慌,这完全是一回事。
现在我们知道我们在谈论什么了,让我们直截了当地说:95个字符对于语言来说真的不算多。它涵盖了英语的基础知识,但是用法语写一封猥亵的信怎么样?德语里的斯特拉·恩恩德隆格斯·格塞茨吗?瑞典语的smörgãsbord的邀请函?嗯,你不能。不能用ASCII。在ASCII中,没有关于如何表示任何字母é、?、ü、ä、ö或§的说明,因此您不能使用它们。
但是看看它,欧洲人说,在一台普通的计算机上,每字节有8位,ASCII浪费了一整位总是被设置为0的位数!(#34;欧洲人说,在一台普通的计算机上,每字节有8位,ASCII是在浪费一整位总是设置为0的位!)。我们可以使用该位将一个完整的128个值再塞到该表中!";于是他们就这么做了。但即便如此,有超过128种方法来划、切、切和点一个元音。并非所有欧洲语言中使用的字母和曲折的所有变体都可以用最多256个值表示在同一表格中。因此,世界最终得到的是丰富的编码方案、标准、事实标准和半标准,它们都涵盖了不同的字符子集。有人需要用捷克语写一份关于瑞典语的文档,发现没有一种编码可以同时涵盖两种语言,于是就发明了一种。或者我是这么想的,它被重复了无数次。
别忘了俄语、印地语、阿拉伯语、希伯来语、韩语和这个星球上目前正在使用的所有其他语言。更不用说那些不再使用的了。一旦您解决了如何用所有这些语言编写混合语言文档的问题,请尝试使用中文。或者日本人。两者都包含数以万计的字符。对于一个由8位组成的字节,您有256个可能的值。去!。
要为使用超过256个字符的语言创建将字符映射到字母的表,一个字节是不够的。使用两个字节(16位),可以对65,536个不同的值进行编码。BIG-5就是这样的双字节编码。它不是将位字符串拆分成8个块,而是将其拆分成16个块,并有一个很大(我是说,很大)的表,指定每个位组合映射到哪个字符。大五码的基本形式主要是繁体字。GB18030是另一种编码,本质上做同样的事情,但同时包括繁体中文字符和简体中文字符。在你问之前,是的,有些编码只包括简体中文。现在不能只有一种编码吗?
GB18030涵盖了相当大范围的字符(包括很大一部分拉丁字符),但最终是众多编码格式中的另一种专用编码格式。
最后,有人受够了这些乱七八糟的东西,开始着手打造一个环来将它们捆绑在一起,创建一个编码标准来统一所有编码标准。该标准是Unicode。它基本上定义了一个包含1,114,112个代码点的巨型表,可用于各种字母和符号。这足以编码所有欧洲人、中东人、远东人、南方人、北方人、西方人、史前学者和人类知道的未来人物。2使用Unicode,您可以使用可以在计算机中键入的任何字符来编写包含几乎任何语言的文档。在Unicode出现之前,这要么是不可能的,要么是非常非常困难的。甚至还有一个非官方的Unicode版本的克林贡语部分。事实上,Unicode足够大,可以容纳非官方的私人使用区域。
那么,Unicode使用多少位来编码所有这些字符呢?没有。因为Unicode不是一种编码。
困惑?很多人似乎都是这样。Unicode首先定义了字符的代码点表。这是一种奇特的说法,65代表A,66代表B,9,731代表☃&34;(说真的,的确如此)。这些代码点如何实际编码成位则是另一个主题。要表示1,114,112个不同的值,两个字节是不够的。三个字节是合适的,但是使用三个字节通常很不方便,所以四个字节是最低要求。但是,除非您实际使用的是中文或其他一些需要大量位进行编码的大数字字符,否则您永远不会使用这四个字节中的大块。如果字母";A";始终编码为00000000 00000000 00000000 01000001,";B&34;始终编码为00000000 00000000 00000000 01000010,依此类推,任何文档都会膨胀到所需大小的四倍。
要优化这一点,有几种方法可以将Unicode代码点编码为位。UTF-32就是这样一种编码,它使用32位对所有Unicode代码点进行编码。也就是说,每个字符四个字节。这很简单,但往往会浪费很多空间。UTF-16和UTF-8是可变长度编码。如果字符可以用单字节表示(因为它的码位非常小),UTF-8将用单字节对其进行编码。如果它需要两个字节,它将使用两个字节,依此类推。它具有使用字节中的最高位来表示一个字符由多少个字节组成的详细方法。这可以节省空间,但如果需要经常使用这些信号位,也可能会浪费空间。UTF-16位于中间,使用至少两个字节,必要时可增加到最多四个字节。
这就是一切。Unicode是将字符映射到数字的大表,不同的UTF编码指定如何将这些数字编码为位。总的来说,Unicode是另一种编码方案。它没有什么特别之处,它只是试图在保持效率的同时涵盖所有内容。这是一件好事。™。
字符由其Unicode代码点";引用。Unicode代码点是用十六进制编写的(为了使数字更短),前面加一个";U+&34;(这就是它们所做的,它除了";这是一个Unicode代码点之外没有其他意义)。字符Ḁ具有UNICODE代码点U+1E00。在其他(十进制)字中,它是Unicode表的第7680个字符。它的正式名称是拉丁文大写字母A,下面有一圈。
综上所述:任何字符都可以用许多不同的位序列编码,任何特定的位序列都可以表示许多不同的字符,这取决于读取或写入它们所使用的编码。原因很简单,因为不同的编码使用不同的每个字符位数和不同的值来表示不同的字符。
话虽如此,我们来看看许多用户和程序员每天遇到的实际问题,这些问题与上述所有问题有何关系,以及他们的解决方案是什么。最大的问题是:
如果你打开一个文档,它看起来是这样的,只有一个原因:你的文本编辑器、浏览器、文字处理器或其他任何试图读取文档的东西都采用了错误的编码。就这些。文档没有损坏(除非损坏,见下文),您不需要使用任何魔术,您只需选择正确的编码来显示文档即可。
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位3开头。如果您没有记错,ASCII不使用该位。所以它不是ASCII。那UTF-8呢?嗯,不,这些序列大多不是有效的UTF-8。4所以UTF-8也被淘汰了。让我们试试Mac Roman(这是为欧洲人准备的另一种编码方案)。嘿,所有这些字节在Mac Roman中都是有效的。10000011映射到";É&34;,01000111映射到";G&34;,依此类推。如果您使用Mac Roman编码读取此位序列,则结果为";ÉGÉ?#34;。这看起来像是一个有效的字符串,不是吗?是?。也许吧?。那么,电脑怎么会知道呢?也许有人想要写下";ÉGÉéÉrä[ÉfÉBÉÉÉO?≠?据我所知那可能是DNA序列。除非你有更好的建议,否则让我们宣布这是一个DNA序列,假设这份文档是用Mac Roman编码的,到此为止。
当然,不幸的是,这完全是无稽之谈。正确的答案是,这个文本是用日语Shift-JIS编码编码的,应该是エンコーディングは難しくない&34;。那么,谁有过这样的想法呢?
文本乱码的主要原因是:有人试图使用错误的编码读取字节序列。计算机总是需要被告知某些文本采用的是什么编码。否则它就不知道了。不同类型的文档可以通过不同的方式指定它们使用的编码方式,并且应该使用这些方式。原始比特序列总是一个神秘的盒子,可以代表任何东西。
大多数浏览器允许在菜单选项“文本编码”下的“视图”菜单中选择不同的编码,这会导致浏览器使用所选编码重新解释当前页面。其他程序可能会提供类似使用编码…重新打开的功能。";在文件菜单中,或可能在";导入…。允许用户手动选择编码的选项。
如果位序列在任何编码中都没有意义(对人类而言),则文档很可能在某个点上转换不正确。假设我们将上述文本保存为UTF-8,因为我们不知道更多信息,所以我们将上面的文本保存为UTF-8。我们不知道上面的文本是什么,所以我们将其保存为UTF-8。因为我们不知道更多信息,所以我们将上面的文本保存为UTF-8。因为我们不知道更多信息,所以我们将上面的文本保存为UTF-8。文本编辑器假定它正确读取了Mac Roman编码的文本,并且您现在希望以不同的编码保存该文本。毕竟,所有这些字符都是有效的Unicode字符。也就是说,在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 1100001110001001111 11000011 10000111 11000011 10010101 11000011 1010110011000011 1001000011 101000011。
这现在是代表文本的Utf-8位序列。这个位序列与我们的原始文档绝对没有任何关系。无论我们尝试用什么编码打开它,我们都无法从中获得文本";エンコーディングは難しくない&34;。它完全迷失了。如果我们知道Shift-JIS文档被误解为Mac Roman,然后意外地另存为UTF-8,并扭转了这一系列失误,那么就有可能从中恢复原始文本。但那将是一次幸运的侥幸。
很多时候,某些位序列在特定编码中是无效的。如果我们尝试使用ASCII打开原始文档,则某些字节在ASCII中是有效的,并映射到实际字符,而其他字节则不会。重新打开它的程序可能会决定以静默方式丢弃在所选编码中无效的任何字节,或可能将它们替换为?还有UNICODE替换字符�(U+FFFD),程序在尝试处理UNICODE时可能会决定为它无法正确解码的任何字符插入该字符。如果在保存文档时删除或替换了一些字符,那么这些字符就真的一去不复返了,没有办法对它们进行反向工程。
如果文档被错误解释并转换为不同的编码,则表示该文档已损坏。尝试修复它可能会成功,也可能不会成功,通常不会成功。任何手动位移位或其他编码巫毒大多都是巫毒。它试图在病人已经死亡后修复症状。
这真的很简单:知道某段文本(即某个字节序列)是什么编码,然后用该编码解释它。这就是你需要做的全部事情。如果您正在编写允许用户输入某些文本的应用程序,请指定您从用户接受的编码。对于任何类型的文本字段,程序员通常可以决定其编码。对于用户可以上传或导入到程序中的任何类型的文件,都需要指定该文件应该采用什么编码。或者,用户需要某种方式来告诉程序文件采用的是什么编码。此信息可能是文件格式本身的一部分,也可能是用户做出的选择(大多数用户通常不会知道,除非他们阅读了本文)。
如果需要从一种编码转换为另一种编码,请使用专门用于此目的的工具干净利落地执行此操作。编码之间的转换是比较两个代码页并确定编码A中的字符152与编码B中的字符4122相同,然后相应地改变比特的乏味任务。这个特殊的轮子不需要重新发明,任何主流编程语言都包括将文本从一种编码转换为另一种编码的方法,而根本不需要考虑代码点、页面或位。
比方说,您的应用程序必须接受以GB18030格式上传的文件,但是在内部您处理的是UTF-32格式的所有数据。像iconv这样的工具可以使用像iconv(';GB18030';,';UTF-32';,$string)这样的一行代码干净利落地转换上传的文件。也就是说,它将保留字符,同时更改底层位:
这就是一切。字符串的内容(即人类可读的字符)没有更改,但它现在是有效的UTF-32字符串。如果你一直把它当作UTF-32,字符乱码是没有问题的。不过,正如一开始所讨论的,并不是所有的编码方案都可以表示所有字符。不能用为欧洲语言设计的任何编码方案对字符";縧&34;进行编码。如果你试着去做坏事™会发生的。
正因为如此,在当今这个时代,几乎没有理由不一路使用Unicode。某些专用编码可能比某些语言的Unicode编码更有效。但是,除非您正在存储数TB的非常专业的文本(而这是大量的文本),否则通常没有理由担心这一点。如今,由不兼容的编码方案引起的问题比浪费的一两个千兆字节严重得多。随着存储空间和带宽不断变大和变得更便宜,这一点将变得更加真实。
如果您系统需要
.