Unicode、UTF-8、和 UTF-16 是什么?

486

Unicode的基础是什么,为什么需要UTF-8或UTF-16?我在谷歌上进行了研究并在这里搜索过,但对我来说不太清楚。

VSS中进行文件比较时,有时会出现两个文件具有不同UTF的消息。为什么会这样?

请用简单的语言解释。


149
听起来您需要阅读《每位软件开发人员绝对必须了解的Unicode和字符集入门知识》(http://www.joelonsoftware.com/articles/Unicode.html)!这是一个非常好的解释,可以让您了解正在发生什么。 - Brian Agnew
5
这个来自官方Unicode网站的常见问题解答可能会为您提供一些答案。 - Nemanja Trifunovic
4
@John:这是一个非常好的介绍,但它不是最终的来源:它省略了一些细节(这对于概述/介绍来说是可以接受的!) - Joachim Sauer
6
这篇文章很好,但有几个错误,并且在某种程度上将UTF-8表现得较为保守。我建议作为补充阅读utf8everywhere.org。 - Pavel Radzivilovsky
2
请查看此网站:http://utf8everywhere.org/ - Vertexwahn
显示剩余5条评论
9个回答

673

为什么我们需要Unicode?

在(不太)早期,只有ASCII。这还好,因为所需的仅是一些控制字符、标点符号、数字和像本句中的字母。不幸的是,当今奇怪的全球互联和社交媒体世界是没有预见到的,因此在同一文档中看到英语、العربية、汉语、עִבְרִית、ελληνικά和ភាសាខ្មែរ并不太罕见(希望我没有破坏任何旧浏览器)。

但出于论证的目的,假设Joe Average是一名软件开发人员。他坚持认为他只需要英语,因此只想使用ASCII。对于用户Joe来说这可能没问题,但对于软件开发人员Joe来说就不行了。大约一半的世界人口使用非拉丁字符,使用ASCII可以说是不考虑这些人,而且他正在关闭他的软件对一个庞大而增长的经济体。

因此,需要一个包含所有语言的全面字符集。于是Unicode应运而生。它为每个字符分配一个称为码点的唯一编号。Unicode相对于其他可能的字符集的优点之一是,前256个代码点与ISO-8859-1和ASCII完全相同。此外,绝大多数常用字符只需用两个字节表示,位于基本多文种平面(BMP)区域内。现在需要一个字符编码来访问这个字符集,正如问题所问,我将专注于UTF-8和UTF-16。

内存考虑

那么在这些编码中,多少字节可以访问哪些字符?

  • UTF-8:
  • 1字节:标准ASCII
  • 2字节:阿拉伯语、希伯来语、大多数欧洲文字(尤其是不包括Georgian
  • 3字节:BMP
  • 4字节:所有Unicode字符
  • UTF-16:
  • 2字节:BMP
  • 4字节:所有Unicode字符

值得一提的是,不在BMP中的字符包括古代文字、数学符号、音乐符号和更罕见的Chinese,Japanese和Korean(CJK)字符。

如果您主要使用ASCII字符,则UTF-8肯定更节省内存。但是,如果您主要使用非欧洲脚本,则使用UTF-8比UTF-16少高达1.5倍的内存效率。当处理大量文本时,例如大型网页或冗长的Word文档,这可能会影响性能。

编码基础知识

注意:如果您知道UTF-8和UTF-16的编码方式,请跳过下一节,直接阅读实际应用部分。
  • UTF-8: 对于标准的ASCII字符(0-127),UTF-8编码是相同的。这使得UTF-8非常适合与现有的ASCII文本进行向后兼容。其他字符需要2到4个字节。通过在每个字节中保留一些位来指示它是多字节字符的一部分来实现这一点。特别地,每个字节的第一个位是1,以避免与ASCII字符冲突。
  • UTF-16: 对于有效的BMP字符,UTF-16表示仅为其代码点。但是,对于非BMP字符,UTF-16引入了代理对。在这种情况下,两个两字节部分的组合映射到一个非BMP字符。这两个两字节部分来自BMP数值范围,但按照Unicode标准保证无效作为BMP字符。此外,由于UTF-16的基本单位是两个字节,因此受endianness的影响。为了补偿这一点,可以在数据流的开头放置一个保留的字节顺序标记,用于指示字节顺序。因此,如果您正在读取UTF-16输入,并且没有指定字节顺序,则必须检查这一点。
作为可以看到的,UTF-8和UTF-16互不兼容。因此,如果您正在进行I/O操作,请确保您知道正在使用哪种编码!有关这些编码的更多详细信息,请参见UTF FAQ
实用编程注意事项
字符和字符串数据类型:它们在编程语言中如何编码?如果它们是原始字节,那么一旦尝试输出非ASCII字符,可能会遇到一些问题。而且,即使字符类型基于UTF,也并不意味着字符串是正确的UTF。它们可能允许非法的字节序列。通常,您将需要使用支持UTF的库,例如C、C++和Java的ICU。无论如何,如果您想要输入/输出其他默认编码之外的内容,您都必须首先进行转换。 推荐、默认和主导编码:当需要选择使用哪种UTF时,通常最好遵循您所在环境的推荐标准。例如,在Web上,UTF-8是主导编码,并且自HTML5以来一直是推荐编码。相反,.NETJava环境都基于UTF-16字符类型。令人困惑的是(也是不正确的),通常会提到“Unicode编码”,这通常指的是给定环境中的主导UTF编码。 库支持:您正在使用的库支持某种编码。哪一个?它们是否支持角落案例?由于需求是发明之母,因此UTF-8库通常会正确支持4字节字符,因为1、2甚至3字节字符经常出现。然而,并非所有所谓的UTF-16库都正确支持代理对,因为它们极少出现。

字符计数: Unicode 中存在组合字符。例如,代码点 U+006E(n)和 U+0303(一个组合的波浪符)形成了 ñ,但代码点 U+00F1 形成了 ñ。它们应该看起来相同,但是简单的计数算法将返回第一个示例的 2,而后者为 1。这不一定是错误的,但也可能不是期望的结果。

比较相等性: A、А 和 Α 看起来相同,但它们分别是拉丁字母、西里尔字母和希腊字母。还有像 C 和 Ⅽ 这样的情况。一个是字母,另一个是罗马数字。此外,我们还需要考虑组合字符。有关更多信息,请参见Duplicate characters in Unicode

代理对: 这些在 Stack Overflow 上经常出现,因此我将提供一些示例链接:


11
很棒的回答,赢得悬赏的机会很大;-) 就个人而言,我会补充说一些人主张将UTF-8作为通用字符编码,但我知道这并不是每个人都认同的观点。 - Joachim Sauer
3
在这个阶段,对我来说仍然太过于技术性了。请问“hello”这个单词在计算机中以UTF-8和UTF-16的形式是如何存储的? - FirstName LastName
1
你能详细解释一下为什么 BMP 在 UTF-8 中占用 3 个字节吗?我本以为由于它的最大值是 0xFFFF(16 位),所以只需要 2 个字节来访问。 - mark
2
@mark 一些位被保留用于编码目的。对于在UTF-8中占用2个字节的代码点,有5个保留位,只剩下11个位来选择一个代码点。U+07FF成为可在2个字节中表示的最高代码点。 - DPenner1
1
顺便提一下,ASCII仅定义了128个代码点,仅使用7位表示。而ISO-8859-1 / ISO-8859-15定义了256个代码点,并使用8位表示。这三种编码的前128个代码点是相同的。 - Tuxdude
显示剩余12条评论

95
  • Unicode
    • 一组在全球范围内使用的字符集
  • UTF-8
    • 一种能够编码Unicode中所有可能字符(称为代码点)的字符编码。
    • 代码单元为8位
    • 使用1到4个代码单元来编码Unicode
    • 00100100代表“$”(一个8位代码单元);11000010 10100010代表“¢”(两个8位代码单元);11100010 10000010 10101100代表“”(三个8位代码单元)
  • UTF-16
    • 另一种字符编码
    • 代码单元为16位
    • 使用1到2个代码单元来编码Unicode
    • 00000000 00100100代表“$”(一个16位代码单元);11011000 01010010 11011111 01100010代表“”(两个16位代码单元)

“two 16-bits” 前的字符无法显示(在 Ubuntu MATE 20.04 (Focal Fossa) 上使用 Firefox 版本 97.0)。 - Peter Mortensen

35
Unicode是一个相当复杂的标准。不必过于害怕,但需要做些功课!因为需要可信的资源,但官方报告很庞大,建议阅读以下内容:
  1. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) 由Joel Spolsky介绍,Stack Exchange CEO撰写的入门文章。
  2. To the BMP and beyond! 由Eric Muller撰写的教程,他曾是Unicode联盟的技术总监,后来担任副总裁(前20张幻灯片足够了)

简单解释:

计算机读取字节,人们读取字符,因此我们使用编码标准将字符映射到字节。 ASCII是第一个广泛使用的标准,但仅涵盖拉丁字符(每个字符使用7位表示最多可以表示128个不同的字符)。 Unicode是一个旨在覆盖世界上所有可能字符的标准(最多可以容纳1,114,112个字符,即每个字符最多使用21位表示。当前的Unicode 8.0总共规定了120,737个字符)。

主要的区别在于ASCII字符可以适配一个字节(8位),但大多数Unicode字符不行。因此需要使用编码格式/方案(如UTF-8和UTF-16),字符模型如下:

每个字符都有一个编号,从0到1,114,111(十六进制:0-10FFFF)称为代码点

一个编码形式将一个码位映射到一个码元序列。一个码元是你想要字符在内存中组织的方式,可以是8位、16位等。UTF-8使用1到4个8位的码元,而UTF-16使用1到2个16位的码元,可以覆盖最大21位的整个Unicode。码元使用前缀以便识别字符边界,而更多的码元表示更多占用位数的前缀。因此,尽管UTF-8仅使用一个字节来表达拉丁文字母,但需要三个字节来表达基本多文种平面内的后来的脚本,而UTF-16对于所有这些脚本都使用两个字节。这是它们的主要区别。
最后,一个编码方案(如UTF-16BE或UTF-16LE)将(序列化)码元序列映射到字节序列。

字符:π
码位:U+03C0
编码形式(码元):
      UTF-8:CF 80
      UTF-16:03C0
编码方案(字节):
      UTF-8:CF 80
      UTF-16BE:03 C0
      UTF-16LE:C0 03

提示:一个十六进制数字表示四个比特,所以两位的十六进制数表示一个字节。
还可以看看维基百科上的平面地图,了解字符集布局。


Joel Spolsky不再是CEO。 - Peter Mortensen

33

文章 《每个程序员绝对需要知道的有关编码和字符集的知识,以便处理文本》 解释了所有详细信息。

向缓冲区写入数据

如果你使用UTF8编码方式向一个4字节大小的缓冲区中写入符号,那么你的二进制数据将会长成这样:

00000000 11100011 10000001 10000010

如果你使用UTF16编码方式向一个4字节大小的缓冲区中写入同样的符号,那么你的二进制数据将会长成这样:

00000000 00000000 00110000 01000010

可以看到,根据内容所用的语言不同,它会影响内存的使用情况。

例如:对于这个特定的符号,使用UTF16编码更高效,因为我们可以利用多余的2个字节来存储下一个符号。但这并不意味着你必须在日语字母中使用UTF16。

从缓冲区读取数据

现在,如果你想读取上述字节,你必须知道它是以哪种编码方式写入的,并正确地解码它。

例如:如果你将这个二进制数据00000000 11100011 10000001 10000010解码成UTF16编码方式,你将得到而不是

注意: 编码和Unicode是两个不同的概念。Unicode是一个大的(表格),其中每个符号都映射到一个唯一的代码点。例如:符号(字母)有一个(代码点)30 42(十六进制)。而编码则是一种算法,将符号转换为更适合存储在硬件中的方式。

30 42 (hex) - > UTF8 encoding - > E3 81 82 (hex), which is above result in binary.

30 42 (hex) - > UTF16 encoding - > 30 42 (hex), which is above result in binary.

在此输入图像描述


非常好的答案,我已经点赞了。您能否检查一下您回答中的这部分是否是您认为应该的方式(因为它没有意义):“将符号转换为更合适的方式”。 - bomben
1
参考文献的标题“每个程序员绝对需要了解编码和字符集以处理文本”与Joel Spolsky的“[软件开发人员绝对必须了解Unicode和字符集的绝对最低限度(无借口!)]”非常相似,几乎涉嫌抄袭。 - Peter Mortensen

22

最初,Unicode旨在具有固定宽度的16位编码(UCS-2)。像Java和Windows NT这样的Unicode早期用户,构建了围绕16位字符串的库。

后来,Unicode的范围扩展到包括历史字符,这需要超过16位编码所支持的65,536个代码点。为了让那些使用UCS-2的平台可以表示额外的字符,引入了UTF-16编码。它使用“代理对”来表示补充平面上的字符。

同时,很多旧软件和网络协议都在使用8位字符串。UTF-8的出现是为了使这些系统可以支持Unicode而不必使用宽字符。它向后兼容7位ASCII。


4
值得注意的是,微软仍然将UTF-16称为Unicode,这增加了混淆的可能性。然而,这两者并不相同。 - Mark Ransom

13

Unicode是一种标准,将所有语言中的字符映射到一个叫做代码点的特定数值。它之所以这样做是因为它允许使用相同的代码点集合来实现不同的编码。

UTF-8和UTF-16就是这样的两种编码方式。它们将代码点作为输入,并使用某些明确定义的公式对其进行编码以生成编码后的字符串。

选择特定的编码取决于您的需求。不同的编码具有不同的内存要求,而且根据您将要处理的字符,您应该选择使用最少字节序列来编码这些字符的编码。

有关Unicode、UTF-8和UTF-16的更详细信息,请参阅此文章:

每个程序员都应该了解的Unicode知识


10

为什么要使用Unicode?因为ASCII只有127个字符。从128到255的字符在不同的国家是不同的,这就是为什么会有代码页。所以他们说:让我们有多达1114111个字符。

那么如何存储最高代码点呢?你需要使用21位来存储它,因此你将使用一个DWORD,有32位和11位浪费。所以如果你使用一个DWORD来存储Unicode字符,这是最简单的方法,因为你DWORD中的值完全匹配代码点。

但是DWORD数组当然比WORD数组大,甚至比BYTE数组还要大。这就是为什么不仅有UTF-32,还有UTF-16的原因。但是UTF-16意味着一个WORD流,而一个WORD有16位,所以最高代码点1114111怎么能适应一个WORD呢?它不能!

所以他们把所有高于65535的东西放入了一个称为替代对的DWORD中。这样的替代对是两个WORDS,并且可以通过查看前6位来检测。

那么UTF-8呢?它是一个字节数组或字节流,但最高代码点1114111怎么能适应一个字节呢?它不能!好吧,他们也放入了一个DWORD,或者可能是一个WORD,对吗?几乎对了!

他们发明了utf-8序列,这意味着每个代码点高于127必须被编码为2字节、3字节或4字节序列。哇!但是我们如何检测这样的序列呢?嗯,一切都在127之前都是ASCII并且是一个单独的字节。以110开头的是两字节序列,以1110开头的是三字节序列,以11110开头的是四字节序列。这些所谓的“起始字节”的剩余位属于代码点。

现在根据序列,必须跟随以下字节。接下来的字节以10开头,其余位是6位有效载荷位,并属于代码点。连接起始字节和后续字节的有效载荷位,就得到了代码点。这就是UTF-8的所有魔力。


5
UTF-8编码下的€(欧元)符号,使用UTF-8三字节序列进行解码:E2=11100010 82=10000010 AC=10101100可以看到,E2以1110开头,因此这是一个三字节序列;同时,82和AC以10开头,所以它们是接下来的字节。现在我们将“有效载荷位”相连: 0010 + 000010 + 101100 = 10000010101100,这是十进制8364。因此,8364必须是€(欧元)符号的代码点。 - brighty

8
ASCII - 软件在内存中为给定字符分配8位字节。对于英语和采用的(像façade这样的外来词)字符,它们相应的十进制值低于128,则能够正常工作。下面是C程序的例子。
UTF-8 - 软件为给定字符分配一个到四个可变的8位字节。这里什么是可变的呢?假设你通过浏览器的HTML页面发送字符'A' (HTML是UTF-8),A的相应十进制值为65,当你将其转换为二进制时,它变成了01000010。这只需要一个字节,即使是特殊的采用的英文字符'ç',如单词façade中所示,也只分配了一个字节的内存。但是,当你想要存储欧洲字符时,需要两个字节,因此需要使用UTF-8。然而,当你使用亚洲字符时,最少需要两个字节,最多需要四个字节。同样,表情符号需要三到四个字节。UTF-8可以满足你的所有需求。
UTF-16会为每个字符分配最少2个字节,最多4个字节,不会分配1或3个字节。每个字符都是以16位或32位表示的。
那么,为什么需要UTF-16呢?最初,Unicode是16位而不是8位。Java采用了原始版本的UTF-16。
简而言之,在你工作的语言或平台已经采用了UTF-16之前,你不需要在任何地方使用它。
由Web浏览器调用的Java程序使用的是UTF-16,但Web浏览器使用的是UTF-8发送字符。

“除非语言或平台已经采用了UTF-16,否则您无需在任何地方使用它。”这是一个很好的观点,但以下是一个非全面列表:JavaScript、Java、.NET、SQL NCHAR、SQL NVARCHAR、VB4、VB5、VB6、VBA、VBScript、NTFS、Windows API…。 - Tom Blodget
除非使用代码页(例如CP-1252),否则当您想要存储欧洲字符时,需要两个字节,因此您需要UTF-8。 - Peter Mortensen
关于“网络浏览器使用UTF-8发送字符”的问题:除非在网页上指定了类似ISO 8859-1的编码方式。例如:<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">。 - Peter Mortensen

3
UTF代表“Unicode转换格式”。基本上,如今的世界上有成百上千种其他语言编写的脚本和以前使用的基本ASCII不包含的格式。因此,UTF应运而生。
UTF-8具有字符编码功能,其代码单元为8位,而UTF-16的代码单元为16位。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接