emoji与自定义表情符号分割问题

发布时间 2023-07-13 18:06:42作者: 木马不是马

在很多ugc场景中,会有用户发布很多表情?,比如这种,还有自定义存储正文为:[捂脸] ,客户端解析展示成这个样子
场景:
在文章社区方面,很可能用户发布了一片长篇大论,里面还可能参杂这各种表情符号,有的机型可能不支持展示,会展示成?或者是小方块或者是空白,在帖子列表页中,实际上接口不需要返回这一个帖子的全部内容,客户端也不需要在列表页展示帖子的全部内容,只有在详情的时候才展示全部,针对这种情况,势必会进行内容截取,比如返回帖子前50个字
实现方式:
伪代码如下:

String str = "天之娇子目中无人,实则醋精,敏感粘人男歌手#\n" +
                "  苏黎烟×沈吟舟\n" +
                "  ——\n" +
                "  苏黎烟因为前男劈腿,跳河自杀,穿越到了一本小说中,成为了和自己同名同姓的女主角。\n" +
                "  因为男主沈吟舟服毒自杀,女主苏黎烟出车祸去世,让苏黎烟再一次穿越回了小说的开始。\n" +
                "  接着她便开始了漫漫追夫路,追夫路上她意外参加了一个选秀综艺,并且C位出道爆火了。\n" +
                "  ——\n" +
                "  苏黎烟:老公好帅,老公我爱你!\n" +
                "  沈吟舟:刚刚在台下你叫我什么?\n" +
                "  苏黎烟:老…老公…\n" +
                "  沈吟舟:哎,老婆乖。\n" +
                "  苏黎烟:???";
        int limit = 50;
        if (str.length() > limit) {
            System.out.println(str.substring(0, limit) + "...");;
        }

结果:

如果没有emoji,一切都没啥问题
含有表情??

String str = "天之娇子目中无人,实则醋精,\uD83E\uDEF5\uD83E\uDEF5敏感粘人男歌手";
        int limit = 15;
        if (str.length() > limit) {
            System.out.println(str.substring(0, limit) + "...");;
        }

分割之后的结果为

?在UTF-8编码中占用2个字符,4个字节,所以这个地方会将这个字符截为两半,大多数地方展示?或者方块(还有部分iphone机型直接解析失败)
所以需要改进一下,这里用offsetByCodePoints代码点的索引来截取,而不能按照字符的长度来截取

offsetByCodePoints返回从index位置开始,返回偏移codePointOffset个代码点之后的索引,具体来说,每个Unicode字符都是由多个代码点组成的,其中一个代码点表示一个字符,Java字符串内部使用UTF-16编码表示Unicode字符,因为对于某些字符,一个代码点可能会横跨两个代码单位,所以修改如下:

/**
     * 字符串截取
     *
     * @param source 字符串
     * @param start  开始位置
     * @param end    结束位置
     * @return
     */
    public static String substring(String source, int start, int end) {
        String result;
        try {
            result = source.substring(source.offsetByCodePoints(0, start),
                    source.offsetByCodePoints(0, end));
        } catch (Exception e) {
            result = source;
        }
        return result;
    }


最后编写自定义字符串工具类

/**
     * 字符串最大长度,缺省显示...
     *
     * @param string 字符串
     * @param length 最大长度
     * @return
     */
    public static String maxLength(CharSequence string, int length) {
        Assert.isTrue(length > 0);
        if (null == string) {
            return null;
        }
        if (string.length() <= length) {
            return string.toString();
        }
        return substring(string.toString(), 0, length) + "...";
    }

但是如果像下面这种情况:

String str = "天之娇子目中无人,实则醋精,[微笑]\uD83E\uDEF5敏感粘人男歌手";

limit为15 之后分割的字符串为 “天之娇子目中无人,实则醋精,[” 后面 [微笑] 其实为自定义表情,但是现在被截取分开了,也是很奇葩,
所以改造如下代码

private static final String BOOK_REGEX = "(\\[微笑\\]|\\[苦笑\\]|\\[擦汗\\]|\\[哭泣\\])";
    private static final Pattern BOOK_PATTERN = Pattern.compile(BOOK_REGEX);

    /**
     * 帖子内容最大长度截取
     *
     * @param string 征文
     * @param length 最大长度
     * @return
     */
    public static String topicMaxLength(CharSequence string, int length) {
        Assert.isTrue(length > 0);
        if (null == string) {
            return null;
        }
        if (string.length() <= length) {
            return string.toString();
        }

        final Matcher matcher = BOOK_PATTERN.matcher(string);
        int end = length;
        while (matcher.find()) {
            if (matcher.start() > length) {
                break;
            }
            if (matcher.end() > length) {
                end = matcher.end();
                break;
            }
        }
        return substring(string.toString(), 0, end) + "...";
    }

/**
     * 字符串截取
     *
     * @param source 字符串
     * @param start  开始位置
     * @param end    结束位置
     * @return
     */
    public static String substring(String source, int start, int end) {
        String result;
        try {
            result = source.substring(source.offsetByCodePoints(0, start),
                    source.offsetByCodePoints(0, end));
        } catch (Exception e) {
            result = source;
        }
        return result;
    }