Unicode码和Emoji表情

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unicode码和Emoji表情相关的知识,希望对你有一定的参考价值。

参考技术A

ASCII 码是 7 位,它将英文字母,数字 0-9 以及一些标点符号和控制字符映射为 0-127 这些整型。

由于 8 位的空间对于各国文字远远不够,因此 Unicode 码诞生。
起初, Unicode 被设计为16位,提供 65536 个字符的空间。当时人们认为这已经大到足够编码世界上现代文本里所有的文字和字符了。

考虑到历史上的文字和比较少使用到的 日本 和 中国 汉字, Unicode 编码扩展到 21 位。

Unicode 的基本元素被称作 编码点(Code Point) 。编码点通过数字来区分,通常写成16进制的形式再加上前缀 "U+" ,所有编码点组成的集合被称作 编码空间(Code Space) 。

Unicode 的编码空间包含 1114112 个编码点。然而,其中只有 128237 个编码点( 12% 的编码空间被赋值),目前,还有很多空间用来增长,同时, Unicode 还保留了另外的 137468 字符作为"自用"空间,这些字符没有标准的含义,可以被个人应用所使用。

为了对编码空间的布局有个了解,把它可视化会比较直观。下面是整个编码空间的布局,一个像素代表一个编码点。使用小方块表示以保证视觉的一致性;每个小方块是 16*16=256 个编码点,每个大方块是一个面有 65536 个编码点,总共加起来有 17 个面板。

第一个面板被称作( 基本多语言面板BMP )。 BMP 包含现代文本所需的基本所有字符,包括拉丁文、斯拉夫文、希腊文、汉字(中国),日文、朝鲜文、阿拉伯文、希伯来文、梵文(印度)等。这个面板就是最长 Unicode 设计所占用的空间( 16 位, 65536 个字符).后来扩展到现在这个规模,然而,大部分现代字符在 BMP 的范围内。

第二个面板则包括历史上的文字,比如苏美尔楔形文字和埃及象形文字以及 emoji 表情.

第三个面板包含了一大块不常用的历史上的汉字字符。

剩下的面板,除了倒数第三个面板中有一小部分被用作格式化字符;倒数两个面板全部保留自用。

为了和以前的 ASCII 兼容, Unicode 的 128 个字符就是 ASCII 的拷贝.

这是 unicode 编码面板中的前三个面板的使用频率图,可以看出使用频率最高的绝大多数分部在 BMP 内,零散的来自第二三个面板。第二个面板下高频率使用的字符则是部分 emoji 表情。

为了解决 unicode 编码占据的内存问题, unicode 就有了几个紧凑的编码。

UTF-32

32 位整数编码,很少被用来存储,因为太占用内存和存储空间。

UTF-8/UTF-16
这两个编码是可变长编码,分别由 8-bit 或 16-bit 为一个单元组成,这些方案中下标值较小的编码点占用的字节数也少,会节省不少内存。

在 UTF-8 中,每个编码点依据下标值,被存储为 1 到 4 个字节。

越是常用的字符,字节越短,最前面的 128 个字符,只使用一个字节表示,与 ASCII 码完全相同。

UTF8 有以下几个好处:

因为这些原因, UTF-8 成为存储和交流 Unicode 文本方面的最佳编码。它也已经是文件格式、网络协议以及 Web API 领域里事实上的标准了。

UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点。

它的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说, UTF-16 的编码长度要么是 2 个字节( U+0000 到 U+FFFF ),要么是 4 个字节( U+010000 到 U+10FFFF )。

于是这里就存在一个问题,当我们遇到两个字符,怎么看出它本身是一个字符,还是需要跟其他两个字节放在一起解读。

我们可以从基本平面看到,从 U+D800 到 U+DFFF 是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。

具体来说,辅助平面的字符位共有 2^20 个,也就是说,对应这些字符至少需要 20 个二进制位。 UTF-16 将这 20 位拆成两半,前 10 位映射在 U+D800 到 U+DBFF (空间大小 2^10 ),称为高位(H),后 10 位映射在 U+DC00 到 U+DFFF (空间大小 2^10 ),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

所以,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00 到 U+DFFF 之间,这四个字节必须放在一起解读。

UTF-32 ,将 Unicode 的每个码点使用 4 个字节表示,字节内容一一对应码点。比如,码点 0 就用 4 个字节的 0 表示,码点 597D 就在前面加两个字节的 0 。

UTF-32 的优点在于,转换规则简单,查找效率高,缺点在于浪费空间,同样内容的英语文本,会比 ASCII 码大四倍。

这个缺点导致实际上没有人使用这种编码方法。

Unicode 包含一个系统,可以合并多个编码点,动态组合字符。此系统用这种方式增加灵活性,而不引起编码点的巨大组合膨胀。

例如,带重音的字符 “Á” 会被表示成由两个编码点组成的字符串: U+0041 “A” 拉丁大写字母 a 加上 U+0301 “◌́” 组合尖音符号。这个字符串自动被渲染成单个字符: “Á” 。

如今, Unicode 还包含许多 “预设的” 编码点,每个表示一个被使用过的组合,例如 U+00C1 “Á” 带锐音符的拉丁大写字母 A 或 U+1EC7 “ệ” 带扬抑符和下点的小写拉丁字母 e 。

Unicode 中,预设字符和动态组合系统并存。后果就是有多种方法表示同一个字符串——不同编码点序列产生相同用户可感知的字符。例如,我们之前看到的,表示字符 “Á” ,我们可以用一个编码点 U+00C1 ,也可以用两个编码点 U+0041 和 U+0301 。要解决这个等值字符串的问题, Unicode 定义了几种形式正规化方法。比如 NFD 和 NFC 。

Unicode 包含多种情况,用户认为的一个"字符"事实上底下可能由多个编码点组成。

Unicode 使用 "字位簇" 的概念来表示这种情况,一个由一个或多个编码点组成的字符串构成一个"用户感知的字符"。

部分的 emoji 的 unicode 长度大于 1 的本质原因是这些 emoji 是字符簇。

从这里我们可以知道 emoji 表情其实是由一个或多个编码点组成的字符串,那我们要怎么判断用户输入的是否为 emoji 表情,又或者判断一个字符串中是否包含 emoji 表情呢。

具体详见下一篇:

Unicode与JavaScript详解
从Emoji的限制到Unicode编码

java处理emoji表情

前言

emoji表情也是使用Unicode编码的,但UTF8编码是不支持的。我们如果想存储emoji到数据库,一般有两种方法,以mysql为例,将数据库编码从 utf8 改为 utf8mb4,第二种就是做一个转换,将emoji表情转换成另一个字符,今天我们就测试一下第二种方法。

实现

我们需要使用第三方开源库emoji-java,github地址

<dependency>
   <groupId>com.vdurmont</groupId>
   <artifactId>emoji-java</artifactId>
   <version>4.0.0</version>
</dependency>

上边为maven地址,测试的3个emoji表情为

将表情转换为别名

import com.vdurmont.emoji.EmojiParser;

public class Client {

  public static void main(String[] args) {
    System.out.println(EmojiParser.parseToAliases("\\uD83D\\uDE04"));
    System.out.println(EmojiParser.parseToAliases("\\uD83D\\uDE38"));
    System.out.println(EmojiParser.parseToAliases("\\uD83D\\uDE1F"));
  }

}

输出结果为

:smile:
:smile_cat:
:worried:

这样数据库就可以存储了

将别名转换为表情

import com.vdurmont.emoji.EmojiParser;

public class Client2 {

  public static void main(String[] args) {
    System.out.println(EmojiParser.parseToUnicode(":smile:"));
    System.out.println(EmojiParser.parseToUnicode(":smile_cat:"));
    System.out.println(EmojiParser.parseToUnicode(":worried:"));
  }

}

输出3个emoji表情,因为markdown文件中不能出现emoji表情,所以这里就不显示了。

将表情转换为HTML格式

import com.vdurmont.emoji.EmojiParser;

public class Client3 {

  public static void main(String[] args) {
    System.out.println(EmojiParser.parseToHtmlHexadecimal("\\uD83D\\uDE04"));
    System.out.println(EmojiParser.parseToHtmlHexadecimal("\\uD83D\\uDE38"));
    System.out.println(EmojiParser.parseToHtmlHexadecimal("\\uD83D\\uDE1F"));
  }

}

输出为

&#x1f604;
&#x1f638;
&#x1f61f;

HTML中显示如下

原理分析

内部维护了一个emoji表情和别名的映射表,使用前缀树保存所有emoji的Unicode编码。

public class EmojiTrie {
  private Node root = new Node();

  public EmojiTrie(Collection<Emoji> emojis) {
    for (Emoji emoji : emojis) {
      Node tree = root;
      for (char c: emoji.getUnicode().toCharArray()) {
        if (!tree.hasChild(c)) {
          tree.addChild(c);
        }
        tree = tree.getChild(c);
      }
      tree.setEmoji(emoji);
    }
  }
  
}

参考

使用轻量级工具emoji-java处理emoji表情字符
特殊字符(包括emoji)梳理和UTF8编码解码原理
emoji表情存储到数据库的方法

以上是关于Unicode码和Emoji表情的主要内容,如果未能解决你的问题,请参考以下文章

新的emoji表情只有iPhone机有么?安卓机能用么

如何将4字节utf-8的emoji表情转换为unicode字符编码

emoji表情处理,emoji表情的编码解码

苹果emoji表情怎么升级

让emoji表情变消失

如何转义emoji表情,让它可以存入utf8的数据库