Unicode 的基础是什么,为什么需要 UTF-8 或 UTF-16?我在谷歌上研究过这个,也在这里搜索过,但我不清楚。
在 VSS 中,在进行文件比较时,有时会出现一条消息,指出两个文件的 UTF 不同。为什么会这样?
请用简单的语言解释。
为什么我们需要Unicode?
在(不太早的)早期,存在的只是 ASCII。这没关系,因为只需要几个控制字符、标点符号、数字和字母,就像这句话中的一样。不幸的是,没有预见到当今全球互通和社交媒体的奇怪世界,在同一份文件中看到英语、العربية、汉语、עִבְרִית、ελληνικά和ភាសាខ្មែរ 并不少见(希望我没有破坏任何旧的浏览器)。
但为了争论,假设 Joe Average 是一名软件开发人员。他坚持认为他永远只需要英语,因此只想使用 ASCII。这对用户 Joe 来说可能很好,但对软件开发人员 Joe 来说就不好了。世界上大约有一半的人使用非拉丁字符,而使用 ASCII 对这些人来说可能是不体谅的,最重要的是,他正在关闭他的软件,以适应一个庞大且不断增长的经济体。
因此,需要一个包含所有语言的包容性字符集。于是出现了Unicode。它为每个字符分配一个唯一编号,称为代码点。与其他可能的集合相比,Unicode 的一个优点是前 256 个代码点与 ISO-8859-1 相同,因此也与 ASCII 相同。此外,绝大多数常用字符仅用两个字节表示,位于称为 Basic Multilingual Plane (BMP) 的区域中。现在需要一个字符编码来访问这个字符集,正如问题所要求的那样,我将专注于 UTF-8 和 UTF-16。
内存注意事项
那么有多少字节可以访问这些编码中的哪些字符呢?
UTF-8:
1 个字节:标准 ASCII
2 个字节:阿拉伯语、希伯来语、大多数欧洲文字(最值得注意的是不包括格鲁吉亚语)
3 个字节:BMP
4 个字节:所有 Unicode 字符
UTF-16:
2个字节:BMP
4 个字节:所有 Unicode 字符
现在值得一提的是,不在 BMP 中的字符包括古代文字、数学符号、音乐符号和更稀有的 Chinese, Japanese, and Korean (CJK) 字符。
如果您主要使用 ASCII 字符,那么 UTF-8 肯定更节省内存。但是,如果您主要使用非欧洲脚本,则使用 UTF-8 的内存效率可能比 UTF-16 低 1.5 倍。在处理大量文本时,例如大型网页或冗长的 word 文档,这可能会影响性能。
编码基础
注意:如果您知道 UTF-8 和 UTF-16 是如何编码的,请跳到下一节了解实际应用。
UTF-8:对于标准 ASCII (0-127) 字符,UTF-8 代码是相同的。如果需要与现有 ASCII 文本向后兼容,这使得 UTF-8 成为理想的选择。其他字符需要 2-4 个字节。这是通过在每个字节中保留一些位来指示它是多字节字符的一部分来完成的。特别是,每个字节的第一位是 1,以避免与 ASCII 字符发生冲突。
UTF-16:对于有效的 BMP 字符,UTF-16 表示只是它的代码点。但是,对于非 BMP 字符,UTF-16 引入了代理对。在这种情况下,两个两字节部分的组合映射到一个非 BMP 字符。这些两字节部分来自 BMP 数字范围,但 Unicode 标准保证作为 BMP 字符无效。另外,由于 UTF-16 以两个字节为基本单位,因此受字节序的影响。作为补偿,可以在数据流的开头放置一个保留的字节顺序标记,以指示字节顺序。因此,如果您正在读取 UTF-16 输入,并且未指定字节顺序,则必须检查这一点。
可以看出,UTF-8 和 UTF-16 几乎无法相互兼容。因此,如果您正在执行 I/O,请确保您知道您使用的是哪种编码!有关这些编码的更多详细信息,请参阅 UTF FAQ。
实际编程注意事项
字符和字符串数据类型:它们在编程语言中是如何编码的?如果它们是原始字节,那么在您尝试输出非 ASCII 字符的那一刻,您可能会遇到一些问题。此外,即使字符类型基于 UTF,也不意味着字符串是正确的 UTF。它们可能允许非法的字节序列。通常,您必须使用支持 UTF 的库,例如用于 C、C++ 和 Java 的 ICU。在任何情况下,如果您想输入/输出默认编码以外的其他内容,则必须先对其进行转换。
推荐,默认和主导编码:在选择使用哪种UTF时,通常最好遵循您正在工作的环境的建议标准。例如,UTF-8在网络,从 HTML5 开始,它一直是 recommended encoding。相反,.NET 和 Java 环境都基于 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 上经常出现,所以我只提供一些示例链接:
获取字符串长度
删除代理对
回文检查
Unicode 是在世界范围内使用的一组字符
是一组在世界范围内使用的字符
UTF-8 一种字符编码,能够以 Unicode 编码所有可能的字符(称为代码点)。代码单元为 8 位 使用一到四个代码单元来编码 Unicode 00100100 表示“$”(一个 8 位);11000010 10100010 表示“¢”(两个 8 位);11100010 10000010 10101100 表示“€”(三个 8 -位)
一种字符编码,能够以 Unicode 编码所有可能的字符(称为代码点)。
代码单元为 8 位
使用一到四个代码单元来编码 Unicode
00100100 代表“$”(一个 8 位);11000010 10100010 代表“¢”(两个 8 位);11100010 10000010 10101100 代表“€”(三个 8 位)
UTF-16 另一种字符编码编码单元是 16 位 使用一到两个编码单元来编码 Unicode 00000000 00100100 为“$”(一个 16 位);11011000 01010010 11011111 01100010 为“𤭢”(两个 16 位)
另一种字符编码
代码单元为 16 位
使用一到两个代码单元来编码 Unicode
00000000 00100100 代表“$”(一个 16 位);11011000 01010010 11011111 01100010 代表“𤭢”(两个 16 位)
Unicode 是一个相当复杂的标准。不要太害怕,但要为一些工作做好准备! [2]
因为总是需要一个可信的资源,但官方的报告是海量的,我建议阅读以下内容:
每个软件开发人员绝对、绝对必须了解 Unicode 和字符集的绝对最低要求(没有任何借口!) Stack Exchange 首席执行官 Joel Spolsky 的介绍。到 BMP 及以后! Eric Muller 的教程,当时的技术总监,后来的副总裁,在 Unicode 联盟(前 20 张幻灯片,你就完成了)
简要说明:
计算机读取字节,人们读取字符,所以我们使用编码标准将字符映射到字节。 ASCII 是第一个广泛使用的标准,但仅涵盖拉丁语(7 位/字符可以表示 128 个不同的字符)。 Unicode 是一个标准,其目标是涵盖世界上所有可能的字符(最多可以容纳 1,114,112 个字符,这意味着最多 21 位/字符。当前的 Unicode 8.0 总共指定了 120,737 个字符,仅此而已)。
主要区别在于 ASCII 字符可以容纳一个字节(八位),但大多数 Unicode 字符不能。因此使用了编码形式/方案(如 UTF-8 和 UTF-16),字符模型如下所示:
每个字符都有一个从 0 到 1,114,111(十六进制:0-10FFFF)的枚举位置,称为 code point。
编码形式 将代码点映射到代码单元序列。 代码单元是您希望字符在内存中组织的方式、8 位单元、16 位单元等等。 UTF-8 使用一到四个 8 位单元,而 UTF-16 使用一到两个 16 位单元,以覆盖最大 21 位的整个 Unicode。单位使用前缀以便可以发现字符边界,更多的单位意味着更多的前缀占用位。因此,虽然 UTF-8 为拉丁文脚本使用一个字节,但它需要三个字节用于 Basic Multilingual Plane 内的后续脚本,而 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
提示:一个十六进制数字代表四位,所以一个两位十六进制数字代表一个字节。
另请查看 plane maps on Wikipedia 以了解字符集布局。
写入缓冲区
如果您写入一个 4 字节的缓冲区,符号 あ
使用 UTF8 编码,您的二进制文件将如下所示:
00000000 11100011 10000001 10000010
如果您写入一个 4 字节的缓冲区,符号 あ
使用 UTF16 编码,您的二进制文件将如下所示:
00000000 00000000 00110000 01000010
如您所见,根据您在内容中使用的语言,这将相应地影响您的记忆。
示例:对于这个特定符号:あ
UTF16 编码更有效,因为我们有 2 个备用字节可用于下一个符号。但这并不意味着您必须对日本字母使用 UTF16。
从缓冲区读取
现在,如果你想读取上面的字节,你必须知道它是用什么编码写入的,并正确解码回来。
例如,如果您将以下内容解码:00000000 11100011 10000001 10000010 为 UTF16 编码,您将得到 臣
而不是 あ
注意: 编码和 Unicode 是两个不同的东西。 Unicode 是大的 (table),每个符号都映射到一个唯一的代码点。例如 あ
符号(字母)有一个 (code point):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.
https://i.stack.imgur.com/6C0C6.png
最初,Unicode 旨在具有固定宽度的 16 位编码 (UCS-2)。 Unicode 的早期采用者,如 Java 和 Windows NT,围绕 16 位字符串构建了他们的库。
后来,Unicode 的范围扩大到包括历史字符,这需要超过 16 位编码支持的 65,536 个代码点。为了允许在使用 UCS-2 的平台上表示附加字符,引入了 UTF-16 编码。它使用“代理对”来表示补充平面中的字符。
同时,许多较旧的软件和网络协议都使用 8 位字符串。 UTF-8 是为了让这些系统可以支持 Unicode 而不必使用宽字符。它向后兼容 7 位 ASCII。
Unicode 是一种将所有语言中的字符映射到称为 code point 的特定数值的标准。这样做的原因是它允许使用相同的代码点集进行不同的编码。
UTF-8 和 UTF-16 就是这样的两种编码。它们将代码点作为输入,并使用一些定义明确的公式对它们进行编码以生成编码字符串。
选择特定的编码取决于您的要求。不同的编码具有不同的内存要求,并且根据您要处理的字符,您应该选择使用最少字节序列来编码这些字符的编码。
有关 Unicode、UTF-8 和 UTF-16 的更多详细信息,您可以查看这篇文章,
What every programmer should know about Unicode
为什么是统一码?因为 ASCII 只有 127 个字符。从 128 到 255 的那些在不同的国家/地区有所不同,这就是为什么会有代码页。所以他们说:让我们最多有 1114111 个字符。
那么如何存储最高代码点呢?您需要使用 21 位存储它,因此您将使用具有 32 位的 DWORD,而浪费了 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 对吗?或者可能是一个词,对吧?几乎正确!
他们发明了 utf-8 序列,这意味着每个高于 127 的代码点都必须编码为 2 字节、3 字节或 4 字节序列。哇!但是我们怎样才能检测到这样的序列呢?嗯,最多 127 的所有内容都是 ASCII 并且是单个字节。 110开头的是两字节序列,1110开头的是三字节序列,11110开头的是四字节序列。这些所谓的“起始字节”的剩余位属于代码点。
现在根据顺序,后面的字节必须跟随。后面的字节以 10 开头,其余位为 6 位有效载荷位,属于代码点。连接起始字节的有效负载位和以下字节/秒,您将获得代码点。这就是 UTF-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,否则您在任何地方都不需要 UTF-16。
Web 浏览器调用的 Java 程序使用 UTF-16,但 Web 浏览器使用 UTF-8 发送字符。
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
UTF 代表 Unicode 转换格式。基本上,在当今世界,存在用数百种其他语言编写的脚本,这些格式未被早期使用的基本 ASCII 涵盖。因此,UTF 应运而生。
UTF-8 具有字符编码能力,其代码单元为 8 位,而 UTF-16 为 16 位。