如果你打开 Python console,输入如下代码
"🇸🇪" in "🇪🇸🇪🇪"
你会惊人的发现,结果是 True
,同样的事情也发生在 JavaScript 中。
什么?
其实这一切都是Unicode的锅。
Unicode
Unicode是一种编码标准,它的目的是电脑能以通用划一的字符集来处理和显示文字,不但减轻在不同编码系统间切换和转换的困扰,更提供了一种跨平台的乱码问题解决方案。
简单的说,Unicode就是给世界上所有的文字符号都用一个唯一的编号替代。大家都用这一个编号,那么就能够显示一样的东西而不会错乱了。这个编号,被称作码位(code points)
码位与字位
码位是什么上面已经解释了,字位(grapheme) 指的是最小的有意义的书写单位。比如 😄
卡
ö
这种都算一个字位,换句话说,人类认为是单个字符的东西,就是字位。
Unicode神奇的地方在于,字位可以由一个或者多个码位组成。
有的时候你能看到有些人的ID很长飘满整个屏幕,或者这种 ҈热҈得҈字҈都҈出҈汗҈了҈ 都是因为Unicode允许多个码位组成一个字位!
比如:
- 💩的码位是
U+1F4A9
,只有这一个 - 🇪🇸的码位是两个区域指示符,E和S
U+1F1EA
U+1F1F8
- 🇪🇪的码位也是两个区指示符,E 和E
U+1F1EA
U+1F1EA
- 🇸🇪也同理,S和E,U+1F1F8 U+1F1EA
- 👨👩👧👦 这个就恐怖了,它是这样的 👨ZWSP👩ZWSP👧ZWSP👦 男女男孩女孩+三个 零宽空格组成
那么 U+1F1EA U+1F1F8 U+1F1EA U+1F1EA
是否包含U+1F1F8 U+1F1EA
?当然了!恰巧在中间呀。美国和厄瓜多尔也包含了瑞典。
字符串的长度
很多情况下我们需要判断字符串的长度,以确保他们不超过指定的范围。那么这个长度是怎么判定的?比如说对于这个恐怖字符 👨👩👧👦是算有多少个 code point,还是占用多少个字节,还是字位?
如果是多少个 code point,那么应该是4+3=7;如果是多少字节,以UTF8为例,那么emoji 4字节,零宽3字节,4*4 + 3*3 = 16 + 9 =25字节
Python
>> len("👨👩👧👦") >> 7
Go
s := "👨👩👧👦" fmt.Println(len(s)) fmt.Println(utf8.RuneCountInString(s))
输出分别为25 和 7
JavaScript
"👨👩👧👦".length 11
没错,JavaScript总是这样独树一帜。因为JavaScript是用UTF-16编码的啦。表情符号是2字节,零宽字符是1,4*2+3*1=11
所以当你在计算最长多少个字符的时候,可要想好了对面是怎么算的。要不然很容易算错哦。
循环字符串
所以循环字符串也要小心了……要不然就很容易会把emoji这种东西凭空劈开。比如说 Python里可以这样处理
import grapheme s = "👨👩👧👦🇸🇪🇨🇳" for i in grapheme.graphemes(s): print(i)
这样就会依次打印三个emoji
或者使用语言对应的ICU,比如 PyICU等。
还有很多痛苦的事情等待你去发现😂
所以,懂了如上道理的你,一定知道为什么 摩纳哥和荷兰包含了中国 了吧?不如打开小红书……
参考阅读
https://blog.xinshijiededa.men/unicode/