资讯专栏INFORMATION COLUMN

前端字符编码小结

gitmilk / 2119人阅读

摘要:导语本文源于微信游戏春节王者摇心愿活动英雄语音祝福自定义输入模块开发过程,对踩过的前端字符编码的坑进行记录总结。只规定了字符编码,而并没有规定具体的编码方式。

导语
本文源于微信游戏春节王者摇心愿活动英雄语音祝福自定义输入模块开发过程,对踩过的前端字符编码的坑进行记录总结。
Unicode 字符
Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。

简单地来说,Unicode 是一种字符编码,它规定用一个码点表示一个字符,其范围为 U+0000~ U+10FFFF , 可以表示超过100万个符号。Unicode 分成17个平面,其中第1个平面称谓基本平面(也称 BMP),其范围为 U+0000~ U+FFFF,另外16个平面称之为辅助平面,每个辅助平面拥有65536(即 2^16)个字符。

Unicode 只规定了字符编码,而并没有规定具体的编码方式。因此就产生了不同的编码方式,包括 UTF-8、UTF-16、UTF-32 等等。

UTF-16

本文主要介绍 UTF-16 编码,不涉及 UTF-8、UTF-32 等其他编码方式,需要扩展阅读请自行查阅。
UTF-16 是一种变长的编码方式,可以用2个字节或者4个字节来编码 Unicode 字符。UTF-16 使用两个字节编码 Unicode 字符中的基本平面的字符,使用 4 个字节编码 Unicode 字符中的辅助平面的字符。

UTF-16 使用变长字节的编码方式,那么如何判断一个字符是基本平面字符还是辅助平面字符?
UTF-16 规定了BMP中,从 U+D800U+DFFF 之间BMP的区段是永久保留不映射到字符,可以利用这段区间来编码辅助平面的字符。
简单来说,从左到右扫描,发现前两个字节不在 U+D800U+DFFF 之UTF间,则可认定这两个字节组成了一个基本平面的字符,发现前两个字节处于 U+D800U+DFFF 之间,则需要读取下两个字节,拼凑成四个字节组成一个辅助平面的字符。

前面提到辅助平面有16(即 2^4)个,每个辅助平面拥有65536(即 2^16)个字符,因此辅助平面共有 2^20个字符,也就是说需要 20 位二进制位来对应这些字符。

16 * 65536 = 2^4 * 2^16 = 2^20

U+D800U+DFFF 之间刚好有 2^11 个码元,因此 UTF-16 使用 U+D800U+DBFF 之间(共有2^10个)码元作为高位, U+DC00U+DFFF 之间(共有 2^10 个)作为低位,这样子高低位 4 个字节组成的编码方式(代理对)就可以表示一个辅助平面的字符了。

其中,辅助平面字符 Unicode 到 UTF-16 代理对的转换规则如下( c 表示 Unicode 的码元,H 表示代理对的高位字节,L 表示代理对的低位字节):

H = Math.floor((c - 0x10000) / 0x400) + 0xD800 
L = (c - 0x10000) % 0x400 + 0xDC00

以上面的音乐字符为例,其 Unicode 字符的码元为 U+1F3B6,可以通过 https://codepoints.net/ 查询到对应字符信息

> H = Math.floor((0x1F3B6 - 0x10000) / 0x400) + 0xD800 
0xd83c

> L = (0x1F3B6 - 0x10000) % 0x400 + 0xDC00
0xdfb6

通过上面的转换规则可以算出其代理对为 ud83cudfb6

UCS-2

UCS-2 是 UTF-16 未出世之前的一种编码方式,可以简单理解为 UTF-16的子集。它采用定长2字节编码,因此只能表示基本平面的字符,对于辅助平面字符,它只能理解为这是 “两个基本平面字符” ,无法正常表示。

javascript的编码方式

好了,进入正题了。前面讲了 UTF-16 和 UCS-2,那么 javascript 到底是采用什么编码的呢?
这个要分情况来讲,javascript 引擎采用 UTF-16 编码,而 javascript 语言本身的设计是采用 UCS-2 编码方式
因此,当我们使用 UCS-2 编码方式设计的 javascript 接口来处理 UTF-16 编码的字符,就会出现很多问题。
比如:

那么如何解决这两者编码方式不一致造成的问题呢,有两种方式:

ES6

新版本的ECMA Script提供了新的API来正确处理字符

正则

利用正则表达式对其修正(项目也是采用这种方式)

var regexAstralSymbols = /[uD800-uDBFF][uDC00-uDFFF]/g
// 获取字符的长度
function countSymbols(string) {
    return string
        // 把代理对改为一个BMP的字符.
        .replace(regexAstralSymbols, "_")
        // …这时候取长度就妥妥的啦.
        .length;
}

// 获取前6个字符
function sliceSymbols(str, limit) {
    var output = [];
    var index = 0;
    var oldStr = str;
    str = str.replace(regexAstralSymbols, function(input, offset, match) {
        if( offset > index ) {
            output = output.concat(match.slice(index, offset).split(""));
        }
        index = offset + input.length;
        output.push(input)
        return "";
    });

    if( index < oldStr.length  ) {
        output = output.concat(oldStr.slice(index, oldStr.length).split(""));
    }
    return output.slice(0, limit).join("");
}

实现效果如下:

上面的解决方法基本可以解决大部分的字符问题,但是在遇到某些emoji表情依然会有些问题。

emoji

emoji表情符号是一种象形文字(图片符号),通常以丰富多彩的形式呈现并在文本中以内联形式使用,起源于日本。Unicode 对 emoji 表情做了划分范围,大部分属于辅助平面字符,目前 Unicode 中收录的 emoji 表情达到了 2700多个。因此,在大部分情况下,使用UTF-16的代理对来处理emoji 表情是没有问题。但在 emoji 表情中,还存在着一些字符(Emoji Sequences),它们没有显示的样式,主要起着连接、控制等作用。目前有下面几种:

控制符

, 作用是让基础Emoji 变成更接近文本样式( text-style )。
, 作用则是让基础Emoji 变成更接近Emoji样式( emoji-style )。

零宽连接符

emoji 除了单个 emoji 符号,还可以通过零宽连接符将多个 emoji 连接成一个 emoji。比如 ud83dudc68是表示一个 man,ud83cudf93表示一个学士帽,这两个通过零宽连接符连接起来 ud83dudc68u200dud83cudf93就表示一个男学生了。

因此,为了解决emoji这些Emoji Sequences,将正则进行扩展:

var regexAstralSymbols = /[uD800-uDBFF][uDC00-uDFFF][u200D|uFE0F|uFE0E]|[uD800-uDBFF][uDC00-uDFFF]/g;

除了以上两种比较常见的 Emoji Sequences,其实还有 Keycap Sequence, Flag Sequence, Tag Sequence, Modifier Sequence等字符,可以参考这里。

参考链接

https://mathiasbynens.be/note...
https://mathiasbynens.be/note...
https://codepoints.net/
http://www.alloyteam.com/2016...
http://unicode.org/emoji/
https://unicode.org/emoji/cha...
http://unicode.org/emoji/char...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92973.html

相关文章

  • js获取字符串字节数方法小结

    摘要:具体如下大家都知道,获取字符串的长度可用来获取,那么获取这段字符串的字节数呢英文字母肯定和字节数都一样都是而中文字节数因此,需要作的就是把中文字符的字节数计算出来。 这篇文章主要介绍了js获取字符串字节数方法,实例总结了javascript字符串长度计算的相关技巧,需要的朋友可以> 参考下 本文实例讲述js获取字符串字节数的方法。分享给大家供大家参考。具体如下: 大家都知道,获取字符串...

    elisa.yang 评论0 收藏0
  • 字符编码(一)

    摘要:最近在看书的时候突然纠结于相关字符编码,查了一些资料,并写了这篇文章,顺带做下笔记,希望能帮到一些人。解决传统的字符编码方案的局限。 最近在看书的时候突然纠结于Unicode相关字符编码,查了一些资料,并写了这篇文章,顺带做下笔记,希望能帮到一些人。文章如果有写的不妥的或者不正确的地方还请大家纠正。 Unicode 编码 Unicode是一个符号集,它对世界上大部分的文字系统进行了整理...

    UnixAgain 评论0 收藏0
  • PHP基础知识小结

    原始数据类型(9种) 基本数据类型: 整形(integer)、浮点型(float)、字符串(string)、布尔型(boolean) 复合数据类型:数组(array)、对象(object)、callable(可调用) 特殊数据类型:资源类型(resource) 和 NULL 变量相关处理函数 is_bool($var)        判断是否为布尔型 is_int($var)    ...

    RancherLabs 评论0 收藏0
  • 前端入门小结

    摘要:实现了搜索这一功能,接下来就是要把这一部分嵌入到一个平台,因此我开始接触编程以及前端。之前我对前端几乎没有什么了解,因此这一周除了体检被检查出来早搏参加入学典礼之外,就是在琢磨,,了,并结合需求开发了网站的一部分。 今年暑假大部分时间是在要学校,前一阶段一直在学习Scala和理解Spark,但是苦于没有实际上手的项目,尽管看了不少论文和书,但不敢说自己理解的有多深刻,所以我打算暂时搁置...

    chengtao1633 评论0 收藏0
  • 第十一章-IO流#yyds干货盘点#

    摘要:是一个系统支持的所有字符的集合,包括各国家文字标点符号图形符号数字等字符集简体中文码表。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等字符集为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码标准万国码。 1 File1.1 File类的概述和构造方法File: 它是文件和目录路径名的抽象...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<