如何去掉 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 pointcode point 起始位code 最后位个数byte分布
1 字节7U+0000U+007F1280xxxxxxx
2 字节11U+0080U+07FF1,920110xxxxx 10xxxxxx
3 字节16U+0800U+FFFF63,4881110xxxx 10xxxxxx 10xxxxxx
4 字节21U+10000U+10FFFF1,048,57611110xxx 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);
    }
  1. 另外一种也可以通过 正则匹配过滤
    public static void main(String[] arg) {
        String str = "中国😘中国";
        System.out.println(str.replaceAll("[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]", ""));
    }

三、小结

本章主要介绍了 utfmb4 相关的以及 过滤一下 四字节字符串的简单方法

支付宝微信
支付宝微信
如果有帮助记得打赏哦特别需要您的打赏哦
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值