一个Emoji的趣闻

2,191次阅读
没有评论

一个Emoji的趣闻

我在虎嗅上看过一篇关于Emoji的文章,特别有意思,在这里跟大家分享一下. 里面提到了Emoji是怎么诞生的。

1999年前后,日本一个名叫栗田穰崇的年轻人,和许多直男一样, 给女友发的短信经常会被误解。比如,“知道了”被解读成“生气了”、“不耐烦了”,随后引发冷战。 于是少年栗田想:“如果能在文字里插入一些表情符号来表达感情,大家应该会需要吧!”原始的Emoji就这么诞生了。

Emoji极大地丰富了我们的生活和通讯交流。Emoji诞生自程序员,但反过来对程序员也造成过一些困扰。

尤其对于面向C端的产品开发者, 用户越来越习惯于输入Emoji, 因此接触Emoji也只会越来越频繁。

Emoji的编码

Emoji字符是Unicode字符集中一部分.

常见的Emoji表情符号在Unicode字符集中的范围和具体的字节映射关系, 可以在Emoji Unicode Tables中查看到. 有意思的是, 该表中还给出了同一个Emoji表情在不同系统或应用中的字体(是字体没错, Emoji的样式可通过字体文件改变)。

一个Emoji的趣闻

关于Emoji的最权威资料, 可以在Unicode® Emoji Charts上查阅到. 截止我写这篇文章的时刻, Emoji Charts 的最新版本是v3.0, v4.0还只是处于Beta阶段.

题外话补充一点: Unicode是一种字符编码方法,它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。 我们所知道的UTF-8、UTF-16等编码, 是对Unicode的不同实现方式. 如果要深入了解更多关于ASCII、Unicode、UTF-8、gb2312、gbk等编码的相关知识,在这里强烈推荐几篇文章,讲得非常好.

一些特殊的Emoji

在众多Emoji中, 有一些特殊的Emoji 并没有显示的样式, 只是起到了控制的作用。这些控制型的Emoji 与基础Emoji 出现在一起, 可以展示更多的样式.

比如 “变量选择器-15″(VARIATION SELECTOR-15, 简写VS-15): <U+FE0E>, 作用是让基础Emoji 变成更接近文本样式(text-style);

而 “变量选择器-16″(VARIATION SELECTOR-16, 简写VS-16): <U+FE0F>, 作用则是让基础Emoji 变成更接近Emoji样式(emoji-style).

前提是, 如果系统支持的话.

VS-15 和 VS-16 是加在基础Emoji字符的后面, 起到控制作用的.

一个Emoji的趣闻

用一段Python代码来演示该例子:

<span class="hljs-comment"># -*- coding: utf-8 -*-</span>
<span class="hljs-comment"># more info to see https://en.wikipedia.org/wiki/Emoji</span>
<span class="hljs-comment"># 符号分别是上图(截图自wiki)中的符号, 最后再加上一个“狗”的Emoji</span>
sample_list = [<span class="hljs-string">u'\u2139'</span>, <span class="hljs-string">u'\u231B'</span>, <span class="hljs-string">u'\u26A0'</span>, <span class="hljs-string">u'\u2712'</span>, <span class="hljs-string">u'\u2764'</span>, <span class="hljs-string">u'\U0001F004'</span>, <span class="hljs-string">u'\U0001F21A'</span>, <span class="hljs-string">u'\U0001f436'</span>, ]

<span class="hljs-comment"># 输出原样式</span>
<span class="hljs-keyword">for</span> code <span class="hljs-keyword">in</span> sample_list:
    <span class="hljs-keyword">print</span> code,
<span class="hljs-keyword">print</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">'-'</span> * <span class="hljs-number">20</span>
<span class="hljs-comment"># 后面加上VS-15</span>
<span class="hljs-keyword">for</span> code <span class="hljs-keyword">in</span> sample_list:
    <span class="hljs-keyword">print</span> (code + <span class="hljs-string">u'\uFE0E'</span>),
<span class="hljs-keyword">print</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">'-'</span> * <span class="hljs-number">20</span>
<span class="hljs-comment"># 后面加上VS-16</span>
<span class="hljs-keyword">for</span> code <span class="hljs-keyword">in</span> sample_list:
    <span class="hljs-keyword">print</span> (code + <span class="hljs-string">u'\uFE0F'</span>),

其输出如下图:

一个Emoji的趣闻

另外, 还有起到控制”表示人身体部位的Emoji”的肤色的控制符, 分别是: <U+1F3FB><U+1F3FF> 共五个, 分别简称为: FITZ-1-2, FITZ-3, FITZ-4, FITZ-5, FITZ-6.

一个Emoji的趣闻

还有一个特殊的控制符: <U+200D> (ZERO WIDTH JOINER, 简写ZWJ), 起到了连接Emoji的作用, 从而将多个Emoji变成一个Emoji来显示. 同样,前提是, 必须系统支持该实现, 否则会忽略.

使用Python代码演示 FITZ-*ZWJ:

<span class="hljs-comment"># -*- coding: utf-8 -*-</span>
<span class="hljs-comment"># more info to see https://en.wikipedia.org/wiki/Emoji</span>

<span class="hljs-comment"># man_list 分别是: 男孩  女孩  男人  女人</span>
man_list = [<span class="hljs-string">u'\U0001F466'</span>, <span class="hljs-string">u'\U0001F467'</span>, <span class="hljs-string">u'\U0001F468'</span>, <span class="hljs-string">u'\U0001F469'</span>]
<span class="hljs-comment"># skin_color_list 分别是: 空字符串,表示默认  白种人 -->(不断加深肤色)  黑种人</span>
skin_color_list = [<span class="hljs-string">''</span>, <span class="hljs-string">u'\U0001F3FB'</span>, <span class="hljs-string">u'\U0001F3FC'</span>, <span class="hljs-string">u'\U0001F3FD'</span>, <span class="hljs-string">u'\U0001F3FE'</span>, <span class="hljs-string">u'\U0001F3FF'</span>, ]
<span class="hljs-keyword">for</span> man <span class="hljs-keyword">in</span> man_list:
    <span class="hljs-keyword">for</span> color <span class="hljs-keyword">in</span> skin_color_list:
        <span class="hljs-keyword">print</span> (man + color),
    <span class="hljs-keyword">print</span>
    <span class="hljs-keyword">print</span> <span class="hljs-string">'-'</span> * <span class="hljs-number">20</span>

<span class="hljs-comment"># Emoji的连接符<U+200D>  (英文名为: ZERO WIDTH JOINER, 简写ZWJ )</span>
<span class="hljs-comment"># 如果系统支持: 连接(男人 + ZWJ + 女人 + ZWJ + 女孩)</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">u'\U0001F468'</span> + <span class="hljs-string">u'\u200D'</span> + <span class="hljs-string">u'\U0001F469'</span> + <span class="hljs-string">u'\u200D'</span> + <span class="hljs-string">u'\U0001F467'</span>
<span class="hljs-comment"># 如果系统不支持: 连接(狗 + ZWJ + 猫 + ZWJ + 老鼠)</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">u'\U0001f436'</span> + <span class="hljs-string">u'\u200D'</span> + <span class="hljs-string">u'\U0001f431'</span> + <span class="hljs-string">u'\u200D'</span> + <span class="hljs-string">u'\U0001f42d'</span>

其输出如下图:

一个Emoji的趣闻

以上内容参考自维基百科

对Emoji 的介绍到该小节结束, 下面内容是一些关于实际中可能遇到的技术问题的解决方法.

MySQL存储Emoji

使用MySQL存储Emoji, 只需要数据表的字符集为utf8mb4即可, 即CHARSET=utf8mb4.

如果想要知道你的MySQL数据库是否支持utf8mb4编码, 可通过show charset; 输出当前安装的MySQL所支持的所有字符集, 查看输出中是否包含有utf8mb4.

另外, 有一些比较老的业务, 可能一开始设计时没考虑到需要支持Emoji, 那就需要修改数据库或数据表的字符集.

查看MySQL说支持的所有字符集
mysql> show charset;

查看某张表当前的字符集
mysql> show create table <table_name>;

创建默认字符集为utf8mb4的数据库.在该数据库中,如果创建表时是不指明字符集,则默认utf8mb4.
mysql> create database <span class="hljs-reserved">default</span> charset utf8mb4;

创建字符集为utf8mb4的表, 数据库的默认字符集非utf8mb4也没问题.
mysql> create table `<span class="javascript"><table_name></span>` (Column定义, Column定义, ...) DEFAULT CHARSET=utf8mb4;

修改已存在的数据库的字符集
mysql> alter database <db_name> <span class="hljs-reserved">default</span> charset = utf8mb4;

修改已存在的表的字符集
mysql> alter table <table_name> <span class="hljs-reserved">default</span> charset = utf8mb4;

如果遇到需要大量修改MySQL字符集的重复工作, 这里介绍一个生成”批量修改字符集语句”的方法, 对输出复制粘贴即可.

使用 mysql -u<your_user_name> -p<your_password> -s --disable-column-names 登录MySQL数据库.

选项-s--silence的意思, 将不会输出绘制表格的字符; --disable-column-names 表示不要输出段名.

mysql> <span class="hljs-function">SELECT <span class="hljs-title">CONCAT</span><span class="hljs-params">(<span class="hljs-string">'alter TABLE '</span>,TABLE_NAME,<span class="hljs-string">' default charset utf8mb4;'</span>)</span> FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA</span>=<span class="hljs-string">'<db_name>'</span> AND TABLE_TYPE=<span class="hljs-string">'BASE TABLE'</span>;

举例查看输出的内容(假设数据库名为 test_db):

mysql> <span class="hljs-function">SELECT <span class="hljs-title">CONCAT</span><span class="hljs-params">(<span class="hljs-string">'alter TABLE '</span>,TABLE_NAME,<span class="hljs-string">' default charset utf8mb4;'</span>)</span> FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA</span>=<span class="hljs-string">'test_db'</span> AND TABLE_TYPE=<span class="hljs-string">'BASE TABLE'</span>;
alter TABLE test_t <span class="hljs-keyword">default</span> charset utf8mb4;
alter TABLE test_t2 <span class="hljs-keyword">default</span> charset utf8mb4;
alter TABLE test_t3 <span class="hljs-keyword">default</span> charset utf8mb4;

使用正则表达式替换/找出字符串中的Emoji

很可惜, Emoji的范围并没有明确的定义. 正如上面提到了, Emoji Charts目前最新版本是v3.0, 未来Emoji的范围还会不断扩大.并且, Emoji 在Unicode的分配中, 并不是连续的区间.

所以, 在这里我只能给出一个可行的区间, 涵盖了基本常见的Emoji, 而且区间中还会包含一些未定义的字符(可能在某些系统会有定义,但是在另外的系统中并没有定义, 毕竟Emoji是商业的产物)。

该匹配规则区间参考了emoji-data.txtUnicode® Technical Report #51, 如下:

<U+<span class="hljs-number">1F</span>300> - <U+<span class="hljs-number">1F</span>5FF>      <span class="hljs-preprocessor"># symbols & pictographs</span>
<U+<span class="hljs-number">1F</span>600> - <U+<span class="hljs-number">1F</span>64F>      <span class="hljs-preprocessor"># emoticons</span>
<U+<span class="hljs-number">1F</span>680> - <U+<span class="hljs-number">1F</span>6FF>      <span class="hljs-preprocessor"># transport & map symbols</span>
<U+<span class="hljs-number">2600</span>>  - <U+<span class="hljs-number">2</span>B55>       <span class="hljs-preprocessor"># other</span>

下面使用Python代码来演示如何使用正则表达式替换(或找出)字符串中的Emoji:

<span class="hljs-comment"># -*- coding: utf-8 -*-</span>
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">try</span>:
    <span class="hljs-comment"># Wide UCS-4 build</span>
    myre = re.compile(<span class="hljs-string">u'['</span>
        <span class="hljs-string">u'\U0001F300-\U0001F64F'</span>
        <span class="hljs-string">u'\U0001F680-\U0001F6FF'</span>
        <span class="hljs-string">u'\u2600-\u2B55]+'</span>,
        re.UNICODE)
<span class="hljs-keyword">except</span> re.error:
    <span class="hljs-comment"># Narrow UCS-2 build</span>
    myre = re.compile(<span class="hljs-string">u'('</span>
        <span class="hljs-string">u'\ud83c[\udf00-\udfff]|'</span>
        <span class="hljs-string">u'\ud83d[\udc00-\ude4f\ude80-\udeff]|'</span>
        <span class="hljs-string">u'[\u2600-\u2B55])+'</span>,
        re.UNICODE)

sss = <span class="hljs-string">u'I have a dog \U0001f436 . You have a cat \U0001f431 ! I smile \U0001f601 to you!'</span>
<span class="hljs-keyword">print</span> myre.sub(<span class="hljs-string">'[Emoji]'</span>, sss)  <span class="hljs-comment"># 替换字符串中的Emoji</span>
<span class="hljs-keyword">print</span> myre.findall(sss)         <span class="hljs-comment"># 找出字符串中的Emoji</span>

输出如下:

I have a dog [Emoji] . You have a cat [Emoji] ! I smile [Emoji] to you!
[<span class="hljs-string">u'\U0001f436'</span>, <span class="hljs-string">u'\U0001f431'</span>, <span class="hljs-string">u'\U0001f601'</span>]

上面例子中, 之所以使用try...except...来处理代码, 是考虑到 UCS-2 (Narrow UCS-2 build) 和 UCS-4 (Wide UCS-4 build) 的区别.

该Demo例子参考了stackoverflow上的精彩回答, 解答了我对此的困惑。

关于UCS-2和UCS-4的区别, 在上面提到的扩展阅读程序员趣味读物:谈谈Unicode编码中有提到, 值得一看.

本文中使用到的示例代码,可以在我的github上下载到.

Emoji可能遇到的字符串截断问题

在Python、JavaScript 这类语言中, 对Unicode的”汉”字取长度时,长度为1.

Python为例:

>>>len(<span class="hljs-string">u'汉'</span>)
<span class="hljs-number">1</span>

但是对于大部分的Emoji(并非全部), 取长度则为2. 比如<U+1f436>(Emoji表情是一只萌萌的狗)

Python:

>>>len(<span class="hljs-string">u'\U0001f436'</span>)
<span class="hljs-number">2</span>

那么, 这就存在一个隐患, 在对字符串进行截断时可能从中间截断, 导致字符显示乱码, 甚至引发报错.

比如继续使用上面例子:

<span class="hljs-prompt">>></span>>u<span class="hljs-string">'这是一只可爱的狗狗\U0001f436'</span>.__len_<span class="hljs-number">_</span>()
<span class="hljs-number">11</span>
<span class="hljs-prompt">>></span>>u<span class="hljs-string">'这是一只可爱的狗狗\U0001f436'</span>[<span class="hljs-number">0</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>]
这是一只可爱的狗狗???

对字符串进行截断,这在实际的开发过程中是非常常见的需求,而且字符串往往可能是用户高度自由的输入内容, 那么包含Emoji的可能性其实是很高的. 一个具体的场景就是: 你们开发了一款社交APP, 允许用户保存文字记录, 然后在应用的某个地方, 又需要显示这些文字记录的摘要,摘要只显示100个字符, 其他省略。 这就会对一串”未知的字符串”进行前100个字符的裁剪。

解决方案也有多种:

  • 全文进行正则匹配, 去掉大部分Emoji, 但是文本长度过长的情况消耗太大, 不值得.
  • 先截取前200个字符, 匹配去掉Emoji再截取100个字符. 貌似可行. 但如果极端条件下前200个字符都是Emoji怎么办? 管他的.
  • 运用上面提到的扩展阅读: 阮一峰博客中提到的UTF-8的编码规则, 对截断后字符串的最后字符进行检查, 发现是截断的字符即进行剔除。该方案可行, 不过你需要自己去实现了。
  • 允许一定概率出现乱码, 乱码就乱码吧,谁叫用户要这样输入的,概率不高, 不会有人发现的。再者, 不影响主要体验。别告诉产品经理就行。将更多精力放在避免其他bug上吧。
admin
版权声明:本站原创文章,由admin2016-11-26发表,共计5570字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)