emoji与自定义表情符号分割问题
在很多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;
}