Emoji的編碼以及常見問題處理

我在虎嗅上看過一篇關於Emoji的趣聞, 特別有意思, 在這裏跟你們分享一下。裏面提到了Emoji是怎麼誕生的。html

1999年先後,日本一個名叫慄田穰崇的年輕人,和許多直男同樣, 給女朋友發的短信常常會被誤解。好比,「知道了」被解讀成「生氣了」、「不耐煩了」,隨後引起冷戰。 因而少年慄田想:「若是能在文字裏插入一些表情符號來表達感情,你們應該會須要吧!」
原始的Emoji就這麼誕生了。

Emoji極大地豐富了咱們的生活和通信交流。Emoji誕生自程序員,但反過來對程序員也形成過一些困擾。
尤爲對於面向C端的產品開發者, 用戶愈來愈習慣於輸入Emoji, 所以處理字符時遇到Emoji也只會愈來愈頻繁。python

Emoji的編碼

Emoji字符是Unicode字符集中一部分. 特定形象的Emoji表情符號對應到特定的Unicode字節。
常見的Emoji表情符號在Unicode字符集中的範圍和具體的字節映射關係, 可經過Emoji Unicode Tables查看到。mysql

有意思的是, 在Emoji Unicode Tables表中,還給出了同一個Emoji表情在不一樣系統中的字體(是字體沒錯, Emoji的樣式可經過字體文件改變)。git

image

關於Emoji的最權威資料, 能夠在Unicode® Emoji Charts上查閱到。
截止我寫這篇文章的時刻, Emoji Charts 的最新版本是v3.0, v4.0還只是處於Beta階段。程序員

題外話補充一點: Unicode是一種字符編碼方法,它是由國際組織設計,能夠容納全世界全部語言文字的編碼方案。
咱們所知道的UTF-八、UTF-16等編碼, 是對Unicode的不一樣實現方式。
若是要深刻了解更多關於ASCII、Unicode、UTF-八、gb23十二、gbk等編碼的相關知識,在這裏強烈推薦幾篇文章,講得很是好。github

一些特殊的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).sql

VS-15 和 VS-16 加在基礎Emoji字符的後面, 能夠起到控制做用(前提是必須系統支持, 不然會被忽略)。數據庫

image

用一段Python代碼來演示該例子:編程

# -*- coding: utf-8 -*-
# more info to see https://en.wikipedia.org/wiki/Emoji
# 符號分別是上圖(截圖自wiki)中的符號, 最後再加上一個「狗」的Emoji
sample_list = [u'\u2139', u'\u231B', u'\u26A0', u'\u2712', u'\u2764', u'\U0001F004', u'\U0001F21A', u'\U0001f436', ]

# 輸出原樣式
for code in sample_list:
    print code,
print
print '-' * 20
# 後面加上VS-15
for code in sample_list:
    print (code + u'\uFE0E'),
print
print '-' * 20
# 後面加上VS-16
for code in sample_list:
    print (code + u'\uFE0F'),

其輸出以下圖, 第一行是原樣式,第二行是加上VS-15後的樣式,第三行是加上VS-16後的樣式:

image

另外, 還有一些控制型的Emoji, 能夠對人體膚色進行改變,改變對象僅限於"表示人身體部位的Emoji".
它們分別是: <U+1F3FB><U+1F3FF> 共五個, 分別簡稱爲: FITZ-1-2, FITZ-3, FITZ-4, FITZ-5, FITZ-6.

image

還有一個特殊的控制符: <U+200D> (ZERO WIDTH JOINER, 簡寫ZWJ), 起到了鏈接Emoji的做用, 從而將多個Emoji變成一個Emoji來顯示. 一樣,前提是必須系統支持, 不然會被忽略.

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

# -*- coding: utf-8 -*-
# more info to see https://en.wikipedia.org/wiki/Emoji

# man_list 分別是: 男孩  女孩  男人  女人
man_list = [u'\U0001F466', u'\U0001F467', u'\U0001F468', u'\U0001F469']
# skin_color_list 分別是: 空字符串,表示默認  白種人 -->(不斷加深膚色)  黑種人
skin_color_list = ['', u'\U0001F3FB', u'\U0001F3FC', u'\U0001F3FD', u'\U0001F3FE', u'\U0001F3FF', ]
for man in man_list:
    for color in skin_color_list:
        print (man + color),
    print
    print '-' * 20

# Emoji的鏈接符<U+200D>  (英文名爲: ZERO WIDTH JOINER, 簡寫ZWJ )
# 若是系統支持: 鏈接(男人 + ZWJ + 女人 + ZWJ + 女孩)
print u'\U0001F468' + u'\u200D' + u'\U0001F469' + u'\u200D' + u'\U0001F467'
# 若是系統不支持: 鏈接(狗 + ZWJ + 貓 + ZWJ + 老鼠)
print u'\U0001f436' + u'\u200D' + u'\U0001f431' + u'\u200D' + u'\U0001f42d'

其輸出以下圖:

image

以上內容參考自維基百科

對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 default charset utf8mb4;

建立字符集爲utf8mb4的表, 數據庫的默認字符集非utf8mb4也沒問題.
mysql> create table `<table_name>` (Column定義, Column定義, ...) DEFAULT CHARSET=utf8mb4;

修改已存在的數據庫的字符集
mysql> alter database <db_name> default charset = utf8mb4;

修改已存在的表的字符集
mysql> alter table <table_name> default charset = utf8mb4;

使用正則表達式匹配Emoji

很惋惜, Emoji的範圍並無明確的定義。正如上面提到了,Emoji Charts目前最新版本是v3.0, 將來Emoji的範圍還會不斷擴大。並且Emoji 在Unicode的分配中並非連續的區間。

因此, 在這裏我只能給出一個可行的匹配區間, 儘量涵蓋了基本常見的Emoj。
該匹配區間中會包含一些未定義的字符, 可能在某些系統會有定義,可是在另外的系統中並無定義。畢竟Emoji是商業的產物。

該匹配規則區間參考了emoji-data.txtUnicode® Technical Report #51, 以下:

<U+1F300> - <U+1F5FF>      # symbols & pictographs
<U+1F600> - <U+1F64F>      # emoticons
<U+1F680> - <U+1F6FF>      # transport & map symbols
<U+2600>  - <U+2B55>       # other

下面使用Python代碼來演示如何使用正則表達式替換(或找出)字符串中的Emoji:

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

sss = u'I have a dog \U0001f436 . You have a cat \U0001f431 ! I smile \U0001f601 to you!'
print myre.sub('[Emoji]', sss)  # 替換字符串中的Emoji
print myre.findall(sss)         # 找出字符串中的Emoji

輸出以下:

I have a dog [Emoji] . You have a cat [Emoji] ! I smile [Emoji] to you!
[u'\U0001f436', u'\U0001f431', u'\U0001f601']

上面例子中, 之因此使用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 這類編程語言中, 一箇中文字符的長度爲1,可是對大部分的Emoji(並不是所有), 取長度則是2。下面使用Python作演示。

以中文的"漢"字取長度爲例,取長度爲1:

>>>len(u'漢')
1

而對於Emoji,以<U+1f436>(該Emoji是一隻萌萌的狗)爲例,取長度爲2:

>>>len(u'\U0001f436')
2

那麼, 這就存在一個隱患, 在對字符串進行截斷時可能從中間截斷, 致使該字符顯示爲亂碼, 甚至引起報錯。

下面例子中, 對字符串進行截取時,正好從<U+1f436>的中間截斷了,出現了亂碼:

>>>u'這是一隻可愛的狗狗\U0001f436'.__len__()
11
>>>u'這是一隻可愛的狗狗\U0001f436'[0:10]
這是一隻可愛的狗狗???

實際場景中,對字符串進行截斷是很是常見的需求,並且字符串每每多是用戶高度自由的輸入內容, 那麼包含Emoji的可能性實際上是很高的。
一個具體的場景就是: 你正在開發了一款社交APP, 容許用戶保存文字記錄, 而後在應用的某個地方, 又須要顯示這些文字記錄的摘要,摘要只顯示用戶輸入的前100個字符, 超出部分用省略號表示。
這種狀況下,就不可避免的可能發生Emoji在中間被截斷的問題。

解決方案也有多種:

  • 全文進行正則匹配, 去掉大部分Emoji, 可是文本長度過長的狀況消耗太大, 不值得.
  • 先截取前200個字符, 匹配去掉Emoji再截取100個字符. 貌似可行. 但若是極端條件下前200個字符都是Emoji怎麼辦? 管他的.
  • 運用上面提到的擴展閱讀: 字符編碼筆記:ASCII,Unicode和UTF-8中提到的UTF-8的編碼規則, 對截斷後字符串的最後字符進行檢查, 發現是截斷的字符即進行剔除。該方案可行, 不過你須要本身去實現了。
  • 容許必定機率出現亂碼, 亂碼就亂碼吧,機率不高,不影響主要體驗。將更多精力放在避免其餘bug上吧。
相關文章
相關標籤/搜索