emoji表情包字符分割问题引出的编码知识

2024-06-06 05:18

本文主要是介绍emoji表情包字符分割问题引出的编码知识,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

经验分享:一个小小emoji尽然牵扯出来这么多东西?

目录

  • 问题
  • 概念常识
    • utf8mb4
    • ASCII码
    • Unicode
      • 平面
      • 表示范围
      • 实现方式
  • 解决emoji截取的问题
    • UTF-16
      • surrogate
    • emoji截取异常原因
    • 问题解决
  • 总结

问题

分享工作中一个真实的案例:
要求显示用户昵称时只能显示第一位和最后一位,其他的用※代替。
例如输入:🐳🐳🐠,输出:🐳***🐠
发现用户昵称包含emoji表情时就会出问题,切割的数据会有问号显示!!

模拟的示例代码如下:
在这里插入图片描述
输出:
在这里插入图片描述

概念常识

要解决这些问题,就必须要铺垫一些基础知识

utf8mb4

一般我们在数据库创建表时都会默认使用这种编码格式:
在这里插入图片描述
当我们想存储emoji数据到数据库中,那么数据库的格式就需要指定为utf8mb4了,要不然存储就会报错了。所以在很多公司的db规范中,数据库默认编码必须为utf8mb4
为何utf8不行而utf8mb4就行?这里面到底有什么弯弯道道?

这里面涉及到unicode相关知识,我们下面会提到,大家继续看。

在mysql 5.5 之前,utf8编码只支持1-3个字节,从mysql 5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,所以能支持更多的字符集。
在这里插入图片描述
这个表格中包含了所有的 emoji 以及它所对应的 unicode编码,同时也有对应的 utf-8编码的实现。

从图中也可以看出 emoji 表情用 utf-8 表示时会占用 4个字节,这也就是为什么数据库用utf8无法存储emoji表情的原因了。

同样我们也可以在java代码中看看emoji占用几个字节长度:
在这里插入图片描述

ASCII码

上面介绍utf8mb4时有提过unicode,介绍它之前我们也需要先提一嘴我们的老朋友:ASCII码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语。

这样我们就可以使用一个字节来表示现代英文,看起来非常不错,部分数据对应关系如下:
在这里插入图片描述
但这个只能显示的代表拉丁文,这显然是远远不够的。

Unicode

ASCII的局限在于只能显示26个基本拉丁字母、阿拉伯数字和英式标点符号,因此只能用于显示现代美国英语。

这时如果能有一种包含了世界上所有的文字的字符集,每一个地区的文字都在这个字符集中有唯一的二进制表示,这样便不会出现乱码问题了。所以Unicode也应运而生了。

Unicode,中文又称万国码、国际码、统一码、单一码,是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。

平面

Unicode 首先承认了 ASCII 占用 0-127 整数资源的合法性,之后又一次占用了 128-65535 的整数资源,有了这么多的整数资源,我们就可以把世界各种文字的每一种字符分配一个整数来表示了。

之后,Unicode 联盟发现 65536 个整数也不够分配的,于是就索性一次性又把之后的 16 个 65536 的数字即 65536-1114111 的整数资源给占了,然后把多占的 16 个 65536 的段分别命名为 16 个平面,加上原来的 0-65535 平面,Unicode 总共有 17 个平面。比如第 1 平面就是 65536-131072。当然,到目前为止,还只分配了 7 个平面出去。
在这里插入图片描述
第0平面(Plane 0),是Unicode中的一个编码区段。编码从U+0000至U+FFFF,这个平面里面的字符是我们最常用到的。

65535 之后分配的字符大多数是 emoji 表情,比如 😺 是 128570(\uD83D\uDE3A)

这里推荐一个在线的编码转换网站:http://ctf.ssleye.com/cencode.html

表示范围

Unicode表示范围:U+0000 ~ U+10FFFF

也就大概是:U+0000~U+110000(加上1),也就是17个FFFF(65535)
差不多17*6w,大概有100w个码点可以用来映射字符
准确的值是 1114,112,差不多112w个码点
最新版本的Unicode含有136,690 个字符,离100w还很远。
Unicode 官方表示目前的码点已经够用,以后不再扩充

实现方式

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。

对于被Unicode收录的字符其编码是唯一且确定的。但是Unicode的实现方式(出于传输、存储、处理或向后兼容的考虑)却有不同的几种,其中最流行的是UTF-8、UTF-16、UCS2、UCS4/UTF-32等,细分的话还有大小端的区别。

对于我们Java而言,可以从char占用2字节来推断出使用的是UTF-16编码来存储

解决emoji截取的问题

言归正传,我们终究还是要解决开头提出的问题,如何正确的截取含有emoji的字符串?这里从UTF-16编码开始说起。

UTF-16

UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。

在基本多语言平面(码位范围U+0000-U+FFFF)内的码位UTF-16编码使用1个码元且其值与Unicode是相等的(不需要转换),这个就是我们正常的汉字,比如在辅助平面(码位范围U+10000-U+10FFFF)内的码位在UTF-16中被编码为一对16bit的码元(即32bit,4字节),称作代理对(surrogate pair)。组成代理对的两个码元前一个称为 前导代理(lead surrogates) 范围为0xD800-0xDBFF,后一个称为 后尾代理(trail surrogates) 范围为0xDC00-0xDFFF

surrogate

上面有提到surrogate,surrogate是代理的意思, 这个概念不是来自 Java 语言,而是来自 Unicode 编码方式之一 UTF-16。具体请见:UTF-16

简而言之,Java 语言内部的字符信息是使用 UTF-16 编码。因为char 这个类型是 16-bit 的。它可以有65536种取值,即65536个编号,每个编号可以代表1种字符。但是,Unicode 包含的字符已经远远超过65536个。那么编号大于65536的,还要用 16-bit 编码,该怎么办?于是Unicode 标准制定组想出的办法就是,从这65536个编号里,拿出2048个,规定它们是「Surrogates」,让它们两个为一组,来代表编号大于65536的那些字符。

更具体地,编号为 U+D800 至 U+DBFF 的规定为「High Surrogates」,共1024个。编号为 U+DC00至 U+DFFF 的规定为「Low Surrogates」,也是1024个。它们两两组合出现,就又可以多表示1048576种字符。

emoji截取异常原因

我们可以把emoji分离出来,如下:

🐳 -> \uD83D\uDC33

🐳 -> \uD83D\uDC33

🐠 -> \uD83D\uDC20

emoji肯定是大于65536的,所以这里就用「High Surrogates」和「Low Surrogates」两两组合的方式来呈现的。

由上面的UTF-16编码知识可以推断出,我们的emoji表情截取一个char后出现乱码的原因,是因为它是属于UTF-16编码辅助平面内的代理对,而我们如果截取时将代理对拆分开 就会出现异常的问题。

对于这种情况,我们可以通过Character类的静态方法isHighSurrogate和isLowSurrogate来判断,单个emoji的组合就是高位+低位,所以对于辅助平面内的代理对,做到整个移除或保留即可。

isHighSurrogate方法的源码如下:

public static final char MIN_HIGH_SURROGATE = '\uD800';public static final char MAX_HIGH_SURROGATE = '\uDBFF';public static boolean isHighSurrogate(char ch) {return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
}

问题解决

实现代码如下:

public static void main(String[] args) {// 用户昵称为:🐳🐳🐠,正常结果应该为:🐳***🐠String context = "\uD83D\uDC33\uD83D\uDC33\uD83D\uDC20";int realNameLength = realStringLength(context);String namePrefix = subString(context, 1, 0);String nameSuffix = subString(context, realNameLength - 1, 1);context = String.format("%s%s%s", namePrefix, "***", nameSuffix);System.out.println(context);
}/*** 包含emoji表情的subString方法** @param str 原有的str* @param len str长度* @param type type = 0 代表prefix,其他代表suffix*/
private static String subString(String str, int len, int type) {if (len < 0) {return str;}int count = 0;for (int i = 0; i < str.length(); i++) {if (count == len) {// type = 0 代表prefix,其他代表suffixif (type == 0) {return str.substring(0, i);}return str.substring(i);}char c = str.charAt(i);if (Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {i++;}count++;}return str;
}/*** 包含emoji表情的字符串实际长度** @param str 原有str* @return str实际长度*/
private static int realStringLength(String str) {int count = 0;for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);if (Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {i++;}count++;}return count;
}

总结

这是一个字符集相关的知识引起的问题。
简单地说,是ASCII码只够显示现代美国英语,如果要包含全世界的所有文字的字符集,就不够了,这时候Unicode应运而生。
一开始Unicode占用了0-65535(即2^16)的整数资源,后来又把后面的16个65536 的数字即 65536-1114111 的整数资源给占了,然后把多占的 16 个 65536 的段分别命名为 16 个平面,加上原来的 0-65535 平面,Unicode 总共有 17 个平面。比如第 1 平面就是 65536-131072。
第0平面(Plane 0,基本多语言平面),是Unicode中的一个编码区段。编码从U+0000至U+FFFF, 这个平面里面的字符是我们最常用到的。65535 之后分配的字符大多数是 emoji 表情,比如 😺 是 128570(\uD83D\uDE3A)。
Java 语言内部的字符信息是使用 UTF-16 编码。因为char 这个类型是 16-bit 的。它可以有65536种取值,即65536个编号,每个编号可以代表1种字符。但是,Unicode 包含的字符已经远远超过65536个。那么编号大于65536的,还要用 16-bit 编码,该怎么办?于是Unicode 标准制定组想出的办法就是,从这65536个编号里,拿出2048个,规定它们是「Surrogates」,让它们两个为一组,来代表编号大于65536的那些字符。所以表情包占了2个字符,Java API也提供了Character.isHighSurrogate和Character.isLowSurrogate,可以用来判断字符串是不是表情包。如果是,处理时就要将2个连续的字符一同处理。

这篇关于emoji表情包字符分割问题引出的编码知识的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035186

相关文章

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

题目1254:N皇后问题

题目1254:N皇后问题 时间限制:1 秒 内存限制:128 兆 特殊判题:否 题目描述: N皇后问题,即在N*N的方格棋盘内放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在同一斜线上。因为皇后可以直走,横走和斜走如下图)。 你的任务是,对于给定的N,求出有多少种合法的放置方法。输出N皇后问题所有不同的摆放情况个数。 输入