最清晰的脚印是留在最泥泞的路上的
大家好,我是柒八九。
前言
提出一个小小的问题。大家按照自己的开发语言的特性,想想结果是啥?
"🤦🏼♂️"这个
Emoji
的长度是多少?
如果,现在你用电脑阅读本文,你可以轻松的打开xx PlayGround
(xx
可以为Js/Java/Rust等
)。然后会得到属于自己语言的结果。
如果,你现在手头没电脑,无法亲自验证,我来直接告诉你答案。上述Emoji
在每种语言环境下的结果都不统一。(当然,有些语言内核使用的机制一样,结果可能也一样)。
也就是说,在编程层面,这不是一种 所见即所得的表现形式。大家这里可能会纳闷了,我要知道这个有啥?现在举一个例子,在前端页面中,我们总是会有统计用户字数的输入框,但是由于用户输入了Emoji
,从用户的角度来看,这就是一个字符,但是在编程层面,如果不做一次解析的话,我们会得到千奇百怪的答案。
然后,我们再来一个让人匪夷所思的例子。在浏览器中,尝试复制如下代码,然后进行观察答案。结果是不是又再一次颠覆你的所学。
"Å" === "Å";
平时,我们时不时的会提到UTF-8/UTF-16/UTF-32
它们到底是个啥?又有啥关系和区别呢?
还有其他的例子就不一一列举了。之所以会出现这么多让人匪夷所思的结果。一切的根源都是Unicode
的闹的。
所以,今天我们就来谈谈这是何方神圣。
在2000
多年前,我们那迷人的老祖宗,秦始皇,就实现了车同轨,书同文,划破地域障碍,从而给不同地方的人在交流上开辟了新的空间。虽然,有些地方还存在十里不同音,百里不通俗的情况(我老家山西就是这种情况)。但是,在官方层面或者书面层面上,大家可以沟通无阻。
好了,天不早了,干点正事哇。
我们能所学到的知识点
- 前置知识点
Unicode
是个啥?UTF-8
又是什么?UTF-32
问题Unicode
病症- 如何检测
扩展形素簇
- "Å" !== "Å" !== "Å"
- Unicode 取决于区域设置
1. 前置知识点
前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。如果大家对这些概念熟悉,可以直接忽略
同时,由于阅读我文章的群体有很多,所以有些知识点可能我视之若珍宝,尔视只如草芥,弃之如敝履。以下知识点,请酌情使用。
ASCll
ASCII(American Standard Code for Information Interchange
)的缩写,发音为ask-key
。ASCII
是一种用于表示字符的7位标准编码
,其中包括字母、数字和标点符号。
7 位编码允许计算机编码总共128个字符
,包括数字 0-9、大写和小写字母 A-Z 以及一些标点符号。然而,这 128 位编码仅适用于英语用户。
ASCII 的功能
ASCII
的建立旨在实现各种数据处理设备之间的兼容性,从而使这些组件能够成功地相互通信。ASCII
使制造商能够生产可以确保在计算机中正确运行的组件。ASCII
使人机互动。
ASCII 在计算机系统中的工作原理
当我们按下键盘上的键,例如字母D
时,电子信号被发送到计算机的CPU
进行处理和存储在内存中。每个字符都被转换为其对应的二进制形式。计算机将字母处理为一个字节,实际上是一系列电子状态的开和关。当计算机完成处理字节后,系统中安装的软件将字节转换回,并在屏幕上显示。字母 D 被转换为01000100
。
TextEncoder 和 TextDecoder
TextEncoder
和 TextDecoder
是 JavaScript
中用于处理字符编码的内置对象。它们通常用于在不同字符编码之间进行文本的编码和解码。
TextEncoder
TextEncoder
是用于将字符串文本编码为字节数组(通常是UTF-8
编码)的对象。- 它提供了一个
encode()
方法,接受一个字符串作为参数,并返回一个包含字节的Uint8Array
对象。 TextEncoder
用于将文本数据转换为字节数据,以便在网络传输、文件读写或其他需要字节数据的情况下使用。
const encoder = new TextEncoder(); const text = "前端柒八九!"; const bytes = encoder.encode(text); // 将文本编码为字节数组
TextDecoder
TextDecoder
是用于将字节数组解码为字符串文本的对象。- 它提供了一个
decode()
方法,接受一个包含字节的Uint8Array
对象,并返回相应的字符串。 TextDecoder
用于将字节数据还原为文本,通常用于处理来自网络请求或文件的字节数据。
示例:
const decoder = new TextDecoder("UTF-8"); const bytes = new Uint8Array([ 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, ]); const text = decoder.decode(bytes); // 将字节数组解码为字符串
这些对象在处理多语言文本、字符编码转换和处理国际化内容时非常有用,使 JavaScript
能够处理不同字符编码之间的数据转换。
Emoji
Emoji
是可以插入文字的图形符号。
它是一个日语词,e
表示"絵",moji
表示"文字"。连在一起,就是"絵文字"。
2010 年,Unicode
开始为 Emoji
分配码点
。也就是说,现在的 Emoji 符号就是一个文字,它会被渲染为图形。
想了解更多,可以翻阅Emoji 简介
2. Unicode 是个啥?
Unicode
是一个旨在统一所有人类语言(包括过去和现在的语言)并使它们与计算机兼容的标准。
Unicode
是一个将不同字符分配给唯一编号的表格。
例如:
- 拉丁字母
A
被分配编号65
。 - 阿拉伯字母 Seen
س
是1587
。 - 片假名字母 Tu
ツ
是12484
- 音乐符号 G 调号
𝄞
是119070
。 💩
是128169
。
Unicode
将这些编号称为码位(code points
)。
由于这套准则是全球都认准的,所以我们采用这套规则,就可以达到书同文的情况,来自不同语言环境下的人,可以阅读彼此的文本。
有如下的关系链子。 一个Unicode
对应着一个字符,并且该字符拥有几乎唯一的码位
。
Unicode
===字符
⟷码位
.
Unicode 有多大?
目前,最大的已定义码位是0x10FFFF
。(0x10FFFF
是一个十六进制数,将其转换为十进制,其值为 1,114,111
。)这给我们提供了大约 110 万个码位的空间。
目前已定义了约 15%
(约 170,000
个),另外 11%
(为私人使用)已被保留。其余约 800,000
个码位目前尚未分配,它们可能在未来成为字符。
大致如下图所示:
大正方形
包含 65,536 个字符。小正方形
包含 256 个字符。- 整个
ASCII
字符集仅占位于左上角的小红色正方形的一半。
私人使用区(Private Use)
私人使用区
是为应用程序开发人员保留的码位
,不会由 Unicode
本身定义。
例如,Unicode
中没有为苹果标志保留位置,因此苹果将它放在了 U+F8FF
,这位于私人使用区
。在任何其他字体中,它将呈现为缺失的字符
,但在与 macOS
一起提供的字体中,我们将看到苹果图标
。
。
私人使用区主要用于图标字体:
U+1F4A9 是什么意思?
这是一种写码位值的约定
。前缀 U+
表示 Unicode
,而 1F4A9
是一个十六进制的码位编号。
U+1F4A9
具体表示的是 💩
。(是不是我们多了一种很委婉的"表扬别人"方式)
3. UTF-8 又是什么?
UTF-8
是一种编码方式。
编码是我们将码位
存储在内存中的方法。在互联网和许多操作系统中,UTF-8
是默认的文本编码。
最简单的 Unicode
编码是 UTF-32
。它将码位简单地存储为 32 位整数。因此,U+1F4A9
变成了 00 01 F4 A9
,占用了四个字节。UTF-32
中的任何其他码位也将占用四个字节。由于最高定义的码位是 U+10FFFF
,因此任何码位都能够容纳。
UTF-8
通常用于存储和传输文本UTF-16
用于某些操作系统和编程语言
UTF-16
被许多系统采用。其中包括Microsoft Windows
、Objective-C
、Java
、JavaScript
、.NET
、Python 2
等
UTF-32
适用于需要直接操作Unicode
代码点的情况
UTF-8 有多少字节?
UTF-8
是一种可变长度的编码方式。
一个码位可能被编码为一个到四个字节的序列。
以下是 UTF-8
编码的表示形式,根据不同的码位范围使用不同数量的字节
码位范围 | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
U+0000..007F | 0xxxxxxx | |||
U+0080..07FF | 110xxxxx | 10xxxxxx | ||
U+0800..FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
U+10000..10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
这些规则描述了如何将不同码位范围内的 Unicode
字符编码为 UTF-8 字节序列
。
如果将这些内容与 Unicode
表结合起来,我们将看到
英语
使用1
个字节进行编码,西里尔字母
、拉丁欧洲语言
、希伯来语
和阿拉伯语
需要2
个字节,中文
、日语
、韩语
、其他亚洲语言和表情符号需要3
或4
个字节。
以下是一些重要的要点:
- 首先,
UTF-8
与ASCII
是字节兼容的。码位0..127
,即旧的ASCII
字符,使用一个字节进行编码,而且它们的字节表示完全相同。例如,U+0041
(A
,拉丁大写字母A
)就是41
,一个字节。
- 任何纯
ASCII
文本也是有效的UTF-8
文本,而且只使用码位0..127
的UTF-8
文本可以直接读取为 ASCII。
- 其次,
UTF-8
对于基本拉丁字符
来说是空间高效的。
- 对于像
HTML
标签或JSON
这样的技术字符串来说,这是有意义的。
- 第三,
UTF-8
内置了错误检测和恢复功能。
- 第一个字节的
前缀
总是与第 2 到第 4 个字节不同。这样,我们始终可以确定是否正在查看完整和有效的UTF-8
字节序列,或者是否有遗漏。 - 然后,我们可以通过
向前
或向后
移动,直到找到正确序列的开头来进行纠正。
还有一些重要的结论:
- 我们无法通过计算字节来确定字符串的长度。
- 我们无法随机跳到字符串的中间并开始阅读。
- 我们无法通过在任意字节偏移处进行切割来获取子字符串,可能会切断字符的一部分。
如果硬要这么做的话,系统会给你一个�
。