UTF-8, UTF-16, and UTF-32

646

UTF-8、UTF-16和UTF-32有什么区别?

我理解它们都可以存储Unicode,并且每种编码使用不同数量的字节来表示字符。是否有选择一种编码比另一种更具优势的情况呢?


70
如果您对Unicode的工作原理感兴趣,请观看这个视频:http://www.youtube.com/watch?v=MijmeoH9LT4。 - user60456
1
这个视频专注于UTF-8,它很好地解释了可变长度编码的工作原理,并且大多数情况下与只读或只写固定长度ASCII的计算机兼容。Unicode的设计者在设计UTF-8编码时非常聪明。 - mins
2
UTF-8是大多数现代软件中保存文件的事实标准。更具体地说,它是HTML、配置和翻译文件(例如Minecraft)中最广泛使用的编码方式(因为Minecraft不接受任何其他编码方式来处理其所有文本信息)。UTF-32对于内部存储器表示来说速度很快,而UTF-16则有点过时,目前仅在Win32中出于历史原因使用(当Windows 95还存在时,UTF-16是固定长度的)。 - Kotauskas
2
@VladislavToncharov,UTF-16从来都不是一个固定长度的编码。你可能把它和UCS-2混淆了。 - user3160514
@Kotauskas JavaScript现在几乎所有的东西都还是使用UTF-16。 - Radvylf Programs
@user60456 - 我点击了链接,看到了Tom Scott,甚至在观看视频之前就自动点赞了你的评论,因为Tom太棒了,有传达信息的天赋。谢谢你分享这个链接。 - GroggyOtter
14个回答

503

UTF-8在大部分字符为ASCII字符的文本块中具有优势,因为UTF-8将这些字符编码为8位(与ASCII相同)。同时,仅包含ASCII字符的UTF-8文件与ASCII文件具有相同的编码。

当ASCII字符不占主导地位时,UTF-16更好,因为它主要使用每个字符2个字节。当UTF-8开始使用3个或更多字节表示高阶字符时,UTF-16仍然保持大部分字符只需2个字节。

UTF-32在4个字节中涵盖所有可能的字符,这使其变得臃肿。我无法想到任何使用它的优点。


220
UTF-32的优势在于:无需对存储的数据进行解码以获得32位Unicode编码点,例如逐个字符处理。该编码点已经在您的数组/向量/字符串中直接可用。 - richq
39
如果(老天保佑)你需要重新实现这个轮子,这也更容易解析。 - Paul McMillan
40
UTF-8在网络传输中有一个优势 - 由于您一次只传输一个字节的数据(而不是4个),因此无需担心字节序问题。 - Tim Čas
40
在UTF-32中,无法进行逐字符处理,因为码点并不总是对应一个字符。 - hamstergene
11
UTF-32的优点:与UTF-8相比,字符串操作可能更快。 - Wes
显示剩余23条评论

424

简而言之:

  • UTF-8:可变宽度编码,向后兼容ASCII。 ASCII字符(U+0000至U+007F)占用1个字节,代码点U+0080到U+07FF占用2个字节,代码点U+0800到U+FFFF占用3个字节,代码点U+10000到U+10FFFF占用4个字节。适合英文文本,不适合亚洲文本。
  • UTF-16:可变宽度编码。 代码点U+0000到U+FFFF占用2个字节,代码点U+10000到U+10FFFF占用4个字节。 对于英文文本不好,对于亚洲文本很好。
  • UTF-32:固定宽度编码。 所有代码点占据四个字节。 占用大量内存,但操作速度快。很少使用。

详细信息请参阅维基百科:UTF-8UTF-16UTF-32


71
@spurrymoses: 我指的严格是数据字节占用的空间量。UTF-8需要3个字节来表示一个亚洲字符,而UTF-16只需要2个字节来表示一个亚洲字符。这并不是一个主要问题,因为与程序内存中存储的平均文本量相比,现代计算机有大量的内存可用。 - Adam Rosenfield
14
UTF-32 不再像以前一样少用了... 在 OSX 和 Linux 上,wchar_t 默认为 4 个字节。gcc有一个选项 -fshort-wchar 可以将其减小到2个字节,但会破坏与标准库的二进制兼容性。 - vine'th
9
当然,UTF-8可以编码任何字符!但是你是否与UTF-16相比较过内存需求?你似乎没有理解重点! - Ustaman Sangat
18
如果有人在包括不能编码Unicode的所有编码格式的背景下说UTF-8“不太适合亚洲文本”,那当然是错误的。但这不是上下文。记忆需求的背景来自于问题(和答案)在比较UTF-8、UTF-16和UTF-32时,它们都能编码亚洲文本,但使用不同数量的内存/存储空间。因此,它们的相对优劣自然完全取决于记忆需求的背景。“不太好”并不等同于“不好”。 - Paul Gregory
8
@McGafter: 当然有可靠的来源。如果你想要可信度,直接去Unicode联盟,查看第2.5章节了解UTF-*编码的描述。但是为了获得简单、高层次的了解,我发现维基百科文章是更易于理解的信息源。 - Adam Rosenfield
显示剩余7条评论

160
  • UTF-8 可变长,使用 1 到 4 个字节。

  • UTF-16 可变长,使用2 或 4个字节。

  • UTF-32 固定长度,使用 4 个字节。


50
UTF8实际上是由1到6个字节组成的。 - Urkle
8
@Urkle 在技术上是正确的,因为映射完整的UTF32/LE/BE范围包括 U-00200000 - U-7FFFFFFF,即使Unicode v6.3在U-0010FFFF(含)结束。以下是如何编码/解码5和6字节UTF8的详细说明:https://lists.gnu.org/archive/html/help-flex/2005-01/msg00030.html - user246672
4
备份这些相关参考资料的部分和它们的来源? - n611x007
32
不,UTF-8不能是5或6字节。Unicode代码点被限制在21位,这将把UTF-8限制为4个字节。(当然,你可以将UTF-8的原则扩展到编码任意大整数,但它不会是Unicode。)请参阅RFC 3629。 - rdb
19
引用维基百科:2003年11月,RFC 3629限制UTF-8以匹配UTF-16字符编码的限制:明确禁止对应于高代理字符和低代理字符的代码点,删除了超过3%的三字节序列,并在U+10FFFF处停止删除了超过48%的四字节序列和所有五字节和六字节序列。 - Adam Calvet Bohl
显示剩余7条评论

108
Unicode定义了一个巨大的字符集,为每个图形符号分配一个独特的整数值(这是一种主要简化,并不实际成立,但对于本问题足够接近)。UTF-8/16/32只是编码这些字符的不同方式。
简单来说,UTF-32为每个字符使用32位值。这使得它们可以为每个字符使用固定宽度的代码。
UTF-16默认使用16位,但仅能提供65k个可能的字符,这远不足以满足完整的Unicode集。因此,一些字符使用一对16位值。
UTF-8默认使用8位值,这意味着前127个值是固定宽度的单字节字符(最高有效位用于表示这是多字节序列的开始,剩余7位用于实际字符值)。所有其他字符都编码为长达4个字节的序列(如果我没有记错)。
这就引出了优点。任何ASCII字符均与UTF-8直接兼容,因此对于升级遗留应用程序,UTF-8是一种常见且明显的选择。在几乎所有情况下,它还将使用最少的内存。另一方面,您无法保证字符的宽度。它可以是1、2、3或4个字符宽,这使得字符串操作很困难。
UTF-32则相反,它使用最多的内存(每个字符宽度固定为4个字节),但另一方面,您知道每个字符都具有这个精确长度,因此字符串操作变得简单得多。您可以仅从字符串的字节数计算出其包含的字符数。对于UTF-8,您无法做到这一点。

UTF-16是一种妥协。它允许大多数字符适合固定宽度的16位值中。因此,只要不涉及汉字、乐符或其他某些字符,可以假设每个字符宽度为16位。它比UTF-32使用更少的内存。但在某些方面它是“两难之最”。它几乎总是比UTF-8使用更多的内存,而且它仍然无法避免困扰UTF-8的问题(可变长度字符)。

最后,通常最好按照平台所支持的方式进行操作。Windows在内部使用UTF-16,因此在Windows上,UTF-16显然是明智的选择。

Linux有点不同,但他们通常会对符合Unicode标准的所有内容使用UTF-8。

简而言之:所有三种编码都可以编码相同的字符集,但它们将每个字符表示为不同的字节序列。


17
说 Unicode 给每个图形符号分配一个唯一的整数是不准确的。它为每个码点分配了一个整数,但有些码点是不可见的控制字符,并且一些图形符号需要多个码点来表示。 - tchrist
21
@tchrist:是的,它不准确。问题在于要准确解释Unicode,需要写成千上万页的内容。我希望能够简单地传达基本概念,以解释编码之间的差异。 - jalf
@jalf 哈哈,对的,基本上要解释Unicode,你得写Unicode核心规范 - Justin Ohms
@tchrist 更具体地说,您可以使用提供的基元构造中文符号(但它们在同一图表中,因此您最终将只是使用不真实的空间 - 无论是磁盘还是RAM - 进行编码),而不是使用内置的符号。 - Kotauskas
2
迄今为止最佳答案 - z33k
2
请注意,UTF-32的描述是不正确的。每个字符并不是4个字节宽。每个码点是4个字节宽,有些字符可能需要多个码点。计算字符串长度不仅仅是将字节数除以4,您必须遍历整个字符串并解码每个码点以解决这些聚类问题。 - CDahn

57

Unicode是一种标准,而UTF-x则可以看作是针对某些实际目的的技术实现:

  • UTF-8 - "大小优化":最适合基于拉丁字符(或ASCII)的数据,每个字符只需1个字节,但随着符号的变化,大小也会相应增长(在最坏情况下可能会增长到每个字符6个字节)
  • UTF-16 - "平衡":每个字符最少需要2个字节,已足以处理主流语言现有字符集,且大小固定,易于处理字符(但大小仍然可变,每个字符最多可增长到4个字节)
  • UTF-32 - "性能":由于具有固定大小字符(4个字节),因此允许使用简单算法,但存在内存缺陷

“主流语言”在世界上很多地方并不那么主流^^ - tuxayo
3
UTF-16实际上是针对非ASCII字符进行优化大小的。这取决于它将用于哪种语言。 - tuxayo
@tuxayo 完全同意,值得注意的是亚洲地区的汉字和日本汉字字符集。 - rook
应该是最佳答案。这个回答太正确了,不能被埋没在这里。 - DexterHaxxor
2
utf-8 可能比所有这些编码方式都更快,因为开发人员花费了最多的精力进行优化。 - qwr

48

我在我的博客文章中尝试给出一个简单的解释。

UTF-32

需要32位(4个字节)来编码任何字符。例如,为了用这种方案表示“A”字符的代码点,你需要用32位二进制数写出65:

00000000 00000000 00000000 01000001 (Big Endian)

仔细观察会发现,最右边的七位实际上是使用ASCII方案时相同的位。但由于UTF-32是一种固定宽度格式,因此我们必须附加三个额外的字节。这意味着,如果我们有两个仅包含字符"A"的文件,一个是ASCII编码,另一个是UTF-32编码,它们的大小分别为1字节和4字节。

UTF-16

许多人认为,由于UTF-32使用固定宽度的32位来表示代码点,所以UTF-16使用了固定宽度的16位。这是错误的!

在UTF-16中,代码点可以用16位或32位表示。因此,这个方案是一种可变长度编码系统。与UTF-32相比,优势在哪里?至少对于ASCII,文件大小不会增加到原来的四倍(但仍然是两倍),因此我们仍然不兼容ASCII。

由于7位足以表示字符"A",因此我们现在可以使用2个字节代替UTF-32的4个字节。它看起来像:

00000000 01000001

UTF-8

在UTF-8编码中,字符的编码长度可能为32、16、24或8位。和UTF-16一样,这也是一种可变长度编码系统。

最终,我们可以用与ASCII编码系统相同的方式来表示“A”:

01001101

UTF-16实际上比UTF-8更好的一个小例子:

考虑中文字符“語” - 它的UTF-8编码为:

11101000 10101010 10011110

虽然它的UTF-16编码更短:

10001010 10011110

为了理解这个表述及其解释,请访问原始文章。


我应该改变编码从UTF-8到UTF-16以适应HTML中的中文字符吗?不是因为UTF-8不能表示中文字符。它可以,但是有些字符会被表示为多个字节,而另一些则仅用一个字节表示。使用UTF-16将所有字符表示为两个字节,这可能会增加文件大小。但是,如果您的网页主要使用中文字符,则可能值得考虑。 - Smart Manoj
计算机如何不会“丢失”包含许多零的UTF-32编码数字?例如,表示“A”将包含26-27个零... - Arik Jordan Graham

23

UTF-8

  • 没有字节顺序的概念
  • 每个字符使用1到4个字节
  • ASCII 是编码的兼容子集
  • 完全自同步。例如,流中任何位置的一个丢失字节最多会破坏一个字符
  • 几乎所有欧洲语言都可以用两个或更少的字节编码一个字符

UTF-16

  • 必须使用已知的字节顺序进行解析或读取字节顺序标记 (BOM)
  • 每个字符使用2或4个字节

UTF-32

  • 每个字符都是4个字节
  • 必须使用已知的字节顺序进行解析或读取字节顺序标记 (BOM)

除非大部分字符来自 CJK(中文,日语和韩语)字符空间,否则 UTF-8 将是最节省空间的。

对于通过字符偏移量随机访问字节数组,UTF-32 是最佳选择。


"self synchronizing" 在 UTF-8 中是如何工作的?你能举出 1 字节和 2 字节字符的例子吗? - Koray Tugay
2
@KorayTugay 有效的较短字节字符串从未用于更长的字符。例如,ASCII 在范围 0-127 内,这意味着所有单字节字符在二进制中具有 0xxxxxxx 的形式。所有双字节字符以 110xxxxx 开头,第二个字节为 10xxxxxx。所以假设一个双字节字符的第一个字符丢失了。一旦你看到 10xxxxxx 而没有前导的 110xxxxxx,你就可以确定一个字节已经丢失或损坏,并且丢弃该字符(或从服务器重新请求它),然后继续直到再次看到有效的第一个字节。 - Chris - Regenerate Response
1
如果您有一个字符的偏移量,那么utf8、utf16或utf32在这种情况下都可以同样地工作;也就是说,它们在通过字符偏移量进行随机访问时同样出色。utf32比utf8更擅长计算字符的想法也是完全错误的。在utf32中,代码点(不是字符,也不是字形..叹气)宽度为32位,在utf8中为8到32位之间,但一个字符可能跨越多个代码点,这破坏了人们声称utf32优于utf8的主要优势。 - Clearer
@Clearer 但是你有多频繁需要处理字符/字形而不仅仅是代码点呢?我曾经参与过许多涉及大量字符串操作的项目,能够在O(1)时间内切片/索引代码点确实非常有帮助。 - Radvylf Programs
@RedwolfPrograms 今天我不从事这方面的工作了,但我曾经在语言分析领域工作过,那里非常重要。 - Clearer

15

在UTF-32中,每个字符都是用32位进行编码。优点是可以轻松计算字符串的长度。缺点是,对于每个ASCII字符,您浪费了额外的三个字节。

在UTF-8中,字符具有可变长度,ASCII字符编码为一个字节(八位),大多数西方特殊字符编码为两个或三个字节(例如€为三个字节),更奇异的字符可以占用最多四个字节。明显的缺点是,您无法先验地计算字符串的长度。但与UTF-32相比,使用拉丁(英语)字母文本需要的字节数要少得多。

UTF-16也具有可变长度。 字符编码为两个或四个字节。我真的看不出有什么意义。它具有可变长度的缺点,但没有像UTF-8那样节省空间的优势。

在这三个中,显然UTF-8是最广泛使用的。


在开发网站时,为什么要计算字符串的长度?选择UTF-8/UTF-16有什么优势吗? - Morfidon
1
优点是您可以轻松计算字符串的长度。如果您通过代码点数定义长度,那么是的,您只需将字节长度除以4即可使用UTF-32获取它。然而,这不是一个非常有用的定义:它可能与字符数无关。此外,规范化可能会改变字符串中代码点的数量。例如,法语单词“été”可以用至少4种不同的方式编码,具有3个不同的代码点长度。 - user3160514
1
UTF-16 可能比 UTF-8 更快,同时也不像 UTF-32 一样浪费内存。 - DexterHaxxor
@MichalŠtein 但它也给你两个世界中最糟糕的东西;它使用比UTF-8更多的空间来存储ASCII,但它也有由于每个字符具有多个码点而引起的所有问题(除了可能的字节序问题)。 - Radvylf Programs

15

我进行了一些测试,比较了MySQL中UTF-8和UTF-16之间的数据库性能。

更新速度

UTF-8

这里输入图片描述

UTF-16

这里输入图片描述

插入速度

这里输入图片描述

这里输入图片描述

删除速度

这里输入图片描述

这里输入图片描述


3
仅仅一个短字符串并没有任何意义,一个记录更是不太有用,时间差可能是由于其他因素或者Mysql自身内部机制造成的。如果想进行可靠的测试,需要使用至少10,000个含200字符的记录进行一系列测试,其中至少包括3个场景,以隔离编码因素。 - danilo

14

我很惊讶这个问题已经有11年历史了,但没有一个答案提到utf-8的#1优点。

utf-8通常即使在不支持utf-8的程序中也能正常工作。这部分是它被设计出来的原因之一。其他答案提到前128个代码点与ASCII相同。所有其他代码点都是由高位设置为1的8位值生成的(值从128到255),因此从非Unicode知道的程序的角度来看,它只是看到字符串是带有一些额外字符的ASCII。

举个例子,假设你写了一个程序添加行号,实际上就像这样(为了保持简单,我们假设行尾只是ASCII 13):

// pseudo code

function readLine
  if end of file
     return null
  read bytes (8bit values) into string until you hit 13 or end or file
  return string

function main
  lineNo = 1
  do {
    s = readLine
    if (s == null) break;
    print lineNo++, s
  }  

将utf-8文件传递给此程序仍然可以正常工作。类似地,以制表符、逗号分隔、解析ASCII引号或其他仅与ASCII值相关的解析都可以使用utf-8正常运行,因为在utf-8中除非它们实际上是那些ASCII值,否则不会出现ASCII值

其他一些答案或评论提到utf-32的优点是您可以单独处理每个码点。例如,这将建议您可以获取像“ABCDEFGHI”这样的字符串,并在每第3个码点处拆分它以进行操作。

ABC
DEF
GHI
这是不正确的。许多码位会影响其他码位,例如颜色选择器码位可让您在 ‍‍‍‍‍ 之间进行选择。如果您随意地在任意码位处分割字符串,您将会破坏这些字符。
另一个例子是双向码位。下面的段落并没有反向输入,只是前面有 0x202E 码位:
  • ‮ 这行文字并没有反向输入,只是显示出来反向罢了。
因此,UTF-32 不会让您毫无顾虑地随意操作 Unicode 字符串,它只允许您使用最基本的代码点而无需额外的代码。
不过,需要注意的是,UTF-8 设计时考虑到通过查看任何单个字节,都可以找到当前代码点或下一个代码点的开始位置。
如果您取 UTF-8 数据中的任意字节,并且其值 < 128,那么它就是正确的代码点。如果其值 >= 128 且 < 192(即最高 2 位为 10),则需要查找前一个字节,直到找到一个其值 >= 192 (即最高 2 位为 11)的字节。在该字节处,您已经找到了代码点的起始位置。该字节编码了随后的多少个字节来表示该代码点。
如果您想找到下一个代码点,只需扫描字节,直到找到值 < 128 或 >= 192 的字节,即为下一个代码点的起始位置。
以下是一张表格,显示了 UTF-8 编码的不同字节数、第一个和最后一个代码点以及每个字节的位:
字节数 第一个代码点 最后一个代码点 字节 1 字节 2 字节 3 字节 4
1 U+0000 U+007F 0xxxxxxx
2 U+0080 U+07FF 110xxxxx 10xxxxxx
3 U+0800 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 U+10000 U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
其中的 xxxxxx 是代码点的位,将每个字节的 xxxx 位连接起来即可得到代码点。

另一个设计特点:UTF-8不知道的代码可以正确地按照字符编码顺序升序排序UTF-8字符串(无论这有多有用)。 - undefined

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