如何去掉 emoji 表情或者四字节字
一、背景
如果我们的数据库是 5.5.3 之前的版本,那么我们设置 字符集为 UTF8 , 这里的utf8 就是 utfmb3(most bytes 3), 就是 最大三个字节,而 有一些字是 使用4字节表示, 比如 emoji 表情 '🍎🍊🍌😘, 还有一些汉字 ‘𠼭’ ,这些都是四字节,在插入数据库时,会提示 报错:
java.sql.SQLException: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation ‘like’
虽然之后的版本升级为支持 utfmb4, 但是由于 数据库量大等各种问题不能 直接修改数据库时, 这时我们就可以 在业务层进行过滤.
二、介绍
2.1 utf8 介绍
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
UTF-8使用一至四个字节为每个字符编码(Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节), 各字节范围如下:
字节 | bits for code point | code point 起始位 | code 最后位 | 个数 | byte分布 |
---|---|---|---|---|---|
1 字节 | 7 | U+0000 | U+007F | 128 | 0xxxxxxx |
2 字节 | 11 | U+0080 | U+07FF | 1,920 | 110xxxxx 10xxxxxx |
3 字节 | 16 | U+0800 | U+FFFF | 63,488 | 1110xxxx 10xxxxxx 10xxxxxx |
4 字节 | 21 | U+10000 | U+10FFFF | 1,048,576 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
这里的code point 解释一下,是一个数字,字符编码集中的一个数, 'A’的code point就是65(Unicode中通常写作 U+0041)
这里主要介绍一下这个图表, 首先 可以看到 :
1个字节 : 一共可以表示 128个 , 从U+0000 —U+007F 的范围 , 对应的byte 填充区域为0xxxxxxx, 也就是 2^7= 128,128个US-ASCII字符只需一个字节编码
2个字节 : 一共可以表示 1,920个 , 从U+0080 —U+07FF 的范围, 对应的byte 填充区域为110xxxxx 10xxxxxx
3个字节 : 一共可以表示 63,488个 , 从U+0800 —U+FFFF 的范围, 对应的byte 填充区域为1110xxxx 10xxxxxx 10xxxxxx
4个字节 : 一共可以表示1,048,576个 , 从U+10000 —U+10FFFF 的范围, 对应的byte 填充区域为11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
2.2 utf-16
对于 4 bytes 的 表示, 0x10000-0x10FFFF(SMP)用两个编码单元(high-surrogates和low-surrogates)表示,high-surrogates范围U+D800-U+DBFF,low-surrogates范围U+DC00-U+DFFF
从 U+D800 到 U+DFFF 是一个空段,即这些码点不对应任何字符。永久保留不映射到Unicode字符。UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码,
这样只需要BMP的数字组合就可以表示整个Unicode字符集。辅助平面的字符位共有 2^20 个,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半:
前10位映射在 U+D800 到 U+DBFF,称为高位代理(H).由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)
后 10 位映射在 U+DC00 到 U+DFFF,称为低位代理(L).由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。
因此,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,并且紧跟在后面的两个字节的码点,在 U+DC00 到 U+DFFF 之间,那么这四个字节必须放在一起解读。
2.3 四字节识别
总结一下 : 如果是四字节 组成的字符,怎么识别呢
1. 第一个字节的code point 是在 U+D800 -- U+DBFF 的之间,紧接着后面的第二个是在 U+DC00 到 U+DFFF 之间,那这个就是 四字节的字符 . 2. 或者利用上面整合之后的 codePoint, 因为四字节的 code point 是在 在 U+10000 - U+10FFFF (65536 -1114111)之间的值, 比如 😘 对应的 code Point 的值是 128536,那就是 四字节 的字符..2.4 java 代码过滤
这里 解释一下, String 里面是一个 数组char[], java中内码中的char使用UTF16的方式编码,一个char占用两个字节, 所以对于三字节都是可以使用的,对于四字节的 就要 两个char ,所以 “中国😘中国”.length() 是 6 ,“中国😘中国”.getBytes().length 是16
“中国😘中国”.length() 是 6 , 这里是由于 String 是一个 char[] 数组, 一个汉字占用一个char, 而 表情是四字节,占有2个char,所以一共 长度为6
“中国😘中国”.getBytes().length 是16 ,这是由于 getBytes() 没有指定字符集,默认是 UTF-8 , “中” 和 “国” 这两个汉字对应的 code point 是在 U+0800 - U+FFFF 之间, 所以需要3 字节表示, emoji 表情是 4字节, 所以是 3+3+4+3+3 = 16
public static void main(String[] arg) {
String str = "中国😘中国";
StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i++) {
int codePoint = str.codePointAt(i);
if (Character.isSupplementaryCodePoint(codePoint)) {
i++;
} else {
sb.appendCodePoint(codePoint);
}
}
String result = sb.toString();
if (!str.equals(result)) {
System.out.println(String.format("the param changed ,the old value %s, the new value %s", str, result));
}
System.out.println(result);
}
这里主要看一下 两个方法String.codePointAt() 和 Character.isSupplementaryCodePoint 的源码, 里面的 逻辑也很简单,就是通过判断 高位是否是在 U+D800 到 U+DBFF之间,低位是否是在:U+DC00 到 U+DFFF 之间
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
static int codePointAtImpl(char[] a, int index, int limit) {
char c1 = a[index];
if (isHighSurrogate(c1) && ++index < limit) {
char c2 = a[index];
if (isLowSurrogate(c2)) {
return toCodePoint(c1, c2);
}
}
return c1;
}
public static boolean isHighSurrogate(char ch) {
// Help VM constant-fold; MAX_HIGH_SURROGATE + 1 == MIN_LOW_SURROGATE
return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
}
public static boolean isLowSurrogate(char ch) {
return ch >= MIN_LOW_SURROGATE && ch < (MAX_LOW_SURROGATE + 1);
}
public static int toCodePoint(char high, char low) {
// Optimized form of:
// return ((high - MIN_HIGH_SURROGATE) << 10)
// + (low - MIN_LOW_SURROGATE)
// + MIN_SUPPLEMENTARY_CODE_POINT;
return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
- (MIN_HIGH_SURROGATE << 10)
- MIN_LOW_SURROGATE);
}
- 另外一种也可以通过 正则匹配过滤
public static void main(String[] arg) {
String str = "中国😘中国";
System.out.println(str.replaceAll("[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]", ""));
}
三、小结
本章主要介绍了 utfmb4 相关的以及 过滤一下 四字节字符串的简单方法
支付宝 | 微信 |
---|---|
![]() | ![]() |
如果有帮助记得打赏哦 | 特别需要您的打赏哦 |