表情符号的组合改变含义 - 1 个字符与 2 个以上字符传递的信息量差异

约 8 分钟阅读

当你从手机的表情键盘选择"👨‍👩‍👧‍👦"时,你以为自己输入了 1 个表情符号。然而这个家庭表情的真面目是"👨 + ZWJ + 👩 + ZWJ + 👧 + ZWJ + 👦"这 7 个码位的连接。外观是 1 个字符,内部是 7 个。在表情符号的世界里,组合方式不同,1 个字符的含义和字符数都会发生巨大变化。

ZWJ 序列 - 用看不见的胶水合并表情符号

ZWJ(Zero Width Joiner)是分配给 Unicode 码位 U+200D 的"零宽度连接符"。它在屏幕上不显示任何内容,但起着将前后表情符号粘合为一体的胶水作用。

机制很简单。将表情 A + ZWJ + 表情 B 排列在一起,如果操作系统或应用有对应这个组合的字形(渲染图像),A 和 B 就会合并显示为一个表情符号。如果没有对应的字形,A 和 B 就原样并排显示。也就是说,ZWJ 序列是一种"能合就合,不能合就照原样"的灵活机制。

ZWJ 序列组成要素码位数显示
👨‍👩‍👧‍👦👨 + ZWJ + 👩 + ZWJ + 👧 + ZWJ + 👦7家庭(父母女儿儿子)
👩‍💻👩 + ZWJ + 💻3女性技术人员
🏳️‍🌈🏳️ + ZWJ + 🌈4彩虹旗
👨‍🍳👨 + ZWJ + 🍳3男性厨师
🧑‍🚀🧑 + ZWJ + 🚀3宇航员
❤️‍🔥❤️ + ZWJ + 🔥4燃烧的心
👩‍❤️‍👨👩 + ZWJ + ❤️ + ZWJ + 👨5情侣

家庭表情是 ZWJ 序列中码位数最多的类别之一。4 人家庭的"👨‍👩‍👧‍👦"有 7 个码位,用 UTF-8 编码后达 25 字节。仅仅 1 个表情符号就消耗了相当于 25 个英文字母的数据量。

ZWJ 序列有趣的地方在于,理论上可以尝试连接任意两个表情符号。输入"🐱 + ZWJ + 🐉"(猫和龙)这样未定义的组合也不会报错。由于没有对应的字形,猫和龙只是并排显示而已。Unicode 联盟官方定义的 ZWJ 序列约有 600 种,但厂商有时也会自行添加支持。

国旗表情 - 2 个地区指示符绘制 1 面旗帜

🇯🇵(日本国旗)看起来是 1 个表情符号,实际上是"地区指示符号字母 J"(U+1F1EF)和"地区指示符号字母 P"(U+1F1F5)这 2 个字符的组合。用专用的 Unicode 字符表示 ISO 3166-1 alpha-2 国家代码"JP"。

国旗国家代码地区指示符码位
🇯🇵JP🇯 + 🇵U+1F1EF U+1F1F5
🇺🇸US🇺 + 🇸U+1F1FA U+1F1F8
🇬🇧GB🇬 + 🇧U+1F1EC U+1F1E7
🇫🇷FR🇫 + 🇷U+1F1EB U+1F1F7
🇧🇷BR🇧 + 🇷U+1F1E7 U+1F1F7
🇰🇷KR🇰 + 🇷U+1F1F0 U+1F1F7

地区指示符号从 A 到 Z 共 26 个,理论上可以有 26 × 26 = 676 种组合。但实际显示为国旗的只有 ISO 3166-1 中注册的约 250 个国家和地区代码。未注册的组合(如"🇽🇽")在不同平台上可能显示为"XX"文本或空白。

这种设计蕴含着政治考量。Unicode 联盟为了避免"哪些地区算作国家"这一政治判断,没有直接定义国旗表情,而是委托给了现有的国际标准 ISO 3166-1。如果新国家独立并在 ISO 3166-1 中注册,无需修改 Unicode 规范就能自动使用该国旗表情。

正如URL 字符数限制中提到的,国家代码在互联网的各种场景中都有使用。ccTLD(国家代码顶级域名)的".jp"也基于同一个 ISO 3166-1 国家代码。

肤色修饰符 - 1 个表情变成 5 种颜色的机制

2015 年 Unicode 8.0 引入的肤色修饰符(Emoji Modifier)用于改变人物表情的肤色。基于皮肤科使用的 Fitzpatrick 量表,提供了 5 个等级的修饰符。

修饰符码位Fitzpatrick 分类示例(👋 + 修饰符)
🏻U+1F3FBType I-II(浅肤色)👋🏻
🏼U+1F3FCType III(较浅肤色)👋🏼
🏽U+1F3FDType IV(中等肤色)👋🏽
🏾U+1F3FEType V(较深肤色)👋🏾
🏿U+1F3FFType VI(深肤色)👋🏿

添加肤色修饰符后,1 个表情变成 2 个码位。"👋"(U+1F44B)是 1 个码位,而"👋🏽"是"U+1F44B U+1F3FD"的 2 个码位。UTF-8 下为 4 字节 + 4 字节 = 8 字节。仅指定肤色就使数据量翻倍。

当 ZWJ 序列与肤色修饰符组合时,码位数会爆炸式增长。例如肤色不同的情侣表情"👩🏻‍❤️‍👨🏿"由 👩 + 🏻 + ZWJ + ❤️ + VS16 + ZWJ + 👨 + 🏿 组成,达 8 个码位。外观是 1 个表情,内部数据量却与英文"Hi there!"(9 个字符)几乎相同。

表情俚语 - 组合产生的隐语世界

表情符号不仅有官方含义,在用户社区中也被当作独特的俚语使用。单独看无害的表情符号,组合后可能产生完全不同的含义。

最著名的大概是 🍑🍆 的组合。桃子和茄子这两个食物表情,在社交媒体上被广泛认知为性暗示。2019 年 Instagram 限制了包含这一组合的帖子的搜索结果。

表情组合字符数官方含义俚语含义
🍑🍆2 字符桃子和茄子性暗示
🧢1 字符棒球帽谎言(cap = 说谎)
💀1 字符骷髅笑死了
🐐1 字符山羊GOAT(Greatest Of All Time)
👁️👄👁️3 字符眼睛和嘴巴震惊、困惑的表情
🫠1 字符融化的脸尴尬、害羞
🤡1 字符小丑做了蠢事的人

"👁️👄👁️"是用 3 个表情排列成脸的"表情艺术":眼 + 嘴 + 眼,表达"难以言喻的表情"。这个 3 字符组合从 2020 年左右开始在 TikTok 上流行,用于表达震惊、困惑或"看到了不该看的东西"的意味。

表情俚语因世代和地区而异。在日本,🙏 用于表示"拜托"或"谢谢",而在欧美有时被解读为"击掌"。正如表情符号的 Unicode 与字符数计算中所述,表情符号的技术字符数与人类感受到的"含义量"完全是不同维度的事情。

各 SNS 平台的表情字符计数方式差异

表情符号的字符数计算在不同平台间差异很大。同一个表情发布在 Twitter(X)和 Instagram 上,消耗的字符数不同。

平台表情计数方式👨‍👩‍👧‍👦 的计数🇯🇵 的计数
Twitter(X)NFC 规范化后的字符数1 字符(= 消耗 2 字符)1 字符(= 消耗 2 字符)
InstagramUTF-16 代码单元数11 字符4 字符
LINE独自计数1 字符1 字符
SMSUCS-2(16 位)7 字符2 字符
JavaScriptUTF-16 代码单元.length = 11.length = 4

Twitter(X)比较宽容,ZWJ 序列的家庭表情和国旗表情都按视觉上的 1 个表情处理(但内部按 2 字符计算权重)。正如Twitter 的字符数限制中详细介绍的,在 280 字符限制内,每个表情按 2 字符计算。

而 JavaScript 的 .length 属性返回 UTF-16 代码单元数,因此包含代理对的表情会返回比视觉字符数更大的值。家庭表情"👨‍👩‍👧‍👦"的 .length 为 11。要获得准确的字符数,可以使用 Array.from(str).length[...str].length,但这些方法也会分解 ZWJ 序列返回 7。要按字素簇(grapheme cluster)计数,需要使用 Intl.Segmenter API。

也可以参考SNS 字符数限制汇总。了解各平台的计数方式差异,可以减少发布大量表情时遇到"字符数超限"的困扰。

表情接龙和电影名猜谜 - 游戏中的字符数

用表情符号玩的游戏从字符数角度来看也有有趣的发现。

"表情接龙"是用表情符号的名称玩日语接龙游戏。🍎(苹果)→ 🦍(大猩猩)→ 🍛(拉面)……以此类推。规则简单,但不知道表情的正式名称就意外地难。例如 🫥 的正式名称是"Dotted Line Face"(虚线脸)。约 3,600 个表情符号全部在 Unicode 的 CLDR(Common Locale Data Repository)中定义了各语言名称。

"用表情猜电影名"也很受欢迎。例如"🦁👑"是"狮子王"(2 个字符表达 3 个字的标题),"👻👻👻🔫"是"捉鬼敢死队"(4 个字符表达 5 个字的标题),"🧙‍♂️💍🌋"是"指环王"(3 个字符表达 3 个字的标题)。表情符号的组合可以说是一种大幅压缩字符数同时传递含义的"超压缩语言"。

在编程中获取表情的"真实字符数"

对开发者来说,表情符号的字符数计算是个头疼的问题,因为不同语言和运行时返回的值各不相同。

语言 / 环境方法"👨‍👩‍👧‍👦"的结果计数单位
JavaScript"👨‍👩‍👧‍👦".length11UTF-16 代码单元
JavaScript[..."👨‍👩‍👧‍👦"].length7码位
Python 3len("👨‍👩‍👧‍👦")7码位
Swift"👨‍👩‍👧‍👦".count1字素簇
Rust"👨‍👩‍👧‍👦".len()25字节(UTF-8)
Golen("👨‍👩‍👧‍👦")25字节(UTF-8)

只有 Swift 返回"1",因为 Swift 采用字素簇(grapheme cluster)作为字符单位。这是最接近人类直觉的结果,但内部处理成本较高。在 JavaScript 中要获得相同结果,需要使用 Intl.Segmenter

正如Unicode 基础知识中所述,"字符数"的定义因上下文而异。表情符号的组合将这个问题展现得最为鲜明。与全角和半角的字符数计算差异一样,请记住表情的计数方式也因平台和语言而异。

表情符号的未来 - 无限的组合可能性

截至 Unicode 16.0(2024 年),表情符号总数约为 3,790 个。但加上 ZWJ 序列和肤色修饰符的组合,可表达的变体达数万种。

2024 年引入了"方向修饰符",可以改变人物表情的朝向。给 🏃(跑步的人)加上方向修饰符变成 🏃‍➡️(向右跑的人)。这也是增加码位数的因素之一。

表情符号的组合大幅提升了文本通信的表达力。日常聊天中不需要在意 1 个表情内部由多少个码位构成。但在有字符数限制的社交媒体发帖、数据库字符数设计、编程中的字符串处理时,"视觉字符数"与"内部字符数"的差距可能成为意想不到的陷阱。

正如LINE 消息字符数一文中提到的,大量使用表情的消息比纯文本消息数据量更大。下次选择表情时,不妨想象一下那 1 个字符背后隐藏着多少个码位。

如果对表情符号和 Unicode 的机制产生了兴趣,可以在 Amazon 上查找相关书籍

分享这篇文章