Emoji与unicode特殊字符的处理
最近遇到了⼀个很让⼈纠结的问题:emoji表情在使⽤的过程中,会莫名其妙的消失,或者变成乱码,同时数据库⽤utf8mb4来存储,但是也出现了问题,冷备过后,导⼊进库的时候,变成了不可见字符,神奇的消失了!查阅了⽹上的解决办法,没有到相应的解决⽅案,于是决定⾃⼰研究unicode,并且处理,发现了⼏个主要知识点:unicode被逻辑分为了17个Plane,每个Plane存65536个代码点。⽽java的 char 最多只有2字节(16 bit),也就是说,他最多只能存储65536个字符,⽽那么问题来了,⼤于0x10000的这些字符怎么处理?很好这个办
法,java也⽤了⼀个⽐较委婉的办法来解决,那么就是dePoint()⽤int来存储。直接看代码吧,代码中有注释解释:
util;
lipse.jetty.util.StringUtil;
lemon.base.Strings;
lemon.hash.Hashing;
/**
* <pre>
* 本类的主要功能是将带有emoji的字符串,格式化成unicode字符串,并且提供可见unicode字符反解成emoji字符
*
*
* 相关识知点:
unicode字符的种类有* <b>
* Unicode平⾯,
* BMP的字符可以使⽤charAt(index)来处理,计数可以使⽤length()
* 其它平⾯字符,需要⽤codePointAt(index),计数可以使⽤codePointCount(0,str.lenght())</b>
*
* Unicode可以逻辑分为17平⾯(Plane),每个平⾯拥有65536( = 216)个代码点,虽然⽬前只有少数平⾯被使
* ⽤。
* 平⾯0 (0000–FFFF): 基本多⽂种平⾯(Basic Multilingual Plane, BMP).
* 平⾯1 (10000–1FFFF): 多⽂种补充平⾯(Supplementary Multilingual Plane, SMP).
* 平⾯2 (20000–2FFFF): 表意⽂字补充平⾯(Supplementary Ideographic Plane, SIP).
* 平⾯3 (30000–3FFFF): 表意⽂字第三平⾯(Tertiary Ideographic Plane, TIP).
* 平⾯4 to 13 (40000–DFFFF)尚未使⽤
* 平⾯14 (E0000–EFFFF): 特别⽤途补充平⾯(Supplementary Special-purpose Plane, SSP)
* 平⾯15 (F0000–FFFFF)保留作为私⼈使⽤区(Private Use Area, PUA)
* 平⾯16 (100000–10FFFF),保留作为私⼈使⽤区(Private Use Area, PUA)
*
* 参考:
* : /wiki/Emoji
* GITHUB: punchdrunker.github.io/iOSEmoji/
* 杂项象形符号:1F300-1F5FF
* 表情符号:1F600-1F64F
* 交通和地图符号:1F680-1F6FF
* 杂项符号:2600-26FF
* 符号字体:2700-27BF
* 国旗:1F100-1F1FF
* 箭头:2B00-2BFF 2900-297F
* 各种技术符号:2300-23FF
* 字母符号: 2100–214F
* 中⽂符号: 303D 3200–32FF 2049 203C
*  Private Use Area:E000-F8FF;
*  High Surrogates D800..DB7F;
*  High Private Use Surrogates  DB80..DBFF
*  Low Surrogates DC00..DFFF  D800-DFFF E000-F8FF
*  标点符号:2000-200F 2028-202F 205F 2065-206F
*  变异选择器:IOS独有 FE00-FE0F
* </pre>
*
* @author Daniel.Zhan
* @version 1.0
* @date 2015年5⽉20⽇
*/
public class EmojiCharacterUtil {
// 转义时标识
private static final char unicode_separator = '&';
private static final char unicode_prefix = 'u';
private static final char separator = ':';
private static boolean isEmojiCharacter(int codePoint) {
return (codePoint >= 0x2600 && codePoint <= 0x27BF) // 杂项符号与符号字体
|| codePoint == 0x303D
|| codePoint == 0x2049
|| codePoint == 0x203C
|| (codePoint >= 0x2000 && codePoint <= 0x200F)//
|| (codePoint >= 0x2028 && codePoint <= 0x202F)//
|| codePoint == 0x205F //
|| (codePoint >= 0x2065 && codePoint <= 0x206F)//
/* 标点符号占⽤区域 */
|| (codePoint >= 0x2100 && codePoint <= 0x214F)// 字母符号
|| (codePoint >= 0x2300 && codePoint <= 0x23FF)// 各种技术符号
|| (codePoint >= 0x2B00 && codePoint <= 0x2BFF)// 箭头A
|| (codePoint >= 0x2900 && codePoint <= 0x297F)// 箭头B
|| (codePoint >= 0x3200 && codePoint <= 0x32FF)// 中⽂符号
|| (codePoint >= 0xD800 && codePoint <= 0xDFFF)// ⾼低位替代符保留区域
|| (codePoint >= 0xE000 && codePoint <= 0xF8FF)// 私有保留区域
|| (codePoint >= 0xFE00 && codePoint <= 0xFE0F)// 变异选择器
|| codePoint >= 0x10000; // Plane在第⼆平⾯以上的,char都不可以存,全部都转
}
/**
* 将带有emoji字符的字符串转换成可见字符标识
*/
public static String escape(String src) {
if (src == null) {
return null;
}
int cpCount = dePointCount(0, src.length());
int firCodeIndex = src.offsetByCodePoints(0, 0);
int lstCodeIndex = src.offsetByCodePoints(0, cpCount - 1);
StringBuilder sb = new StringBuilder(src.length());
for (int index = firCodeIndex; index <= lstCodeIndex;) {
int codepoint = dePointAt(index);
if (isEmojiCharacter(codepoint)) {
String hash = HexString(codepoint);
sb.append(unicode_separator).append(hash.length()).append(unicode_prefix).append(separator).append(hash);
} else {
sb.append((char) codepoint);
}
}
String();
}
/** 解析可见字符标识字符串 */
public static String reverse(String src) {
// 查对应编码的标识位
if (src == null) {
return null;
}
StringBuilder sb = new StringBuilder(src.length());
char[] sourceChar = CharArray();
int index = 0;
while (index < sourceChar.length) {
if (sourceChar[index] == unicode_separator) {
if (index + 6 >= sourceChar.length) {
sb.append(sourceChar[index]);
index++;
continue;
}
/
/ ⾃已的格式,与通⽤unicode格式不能互转
if (sourceChar[index + 1] >= '4' && sourceChar[index + 1] <= '6' && sourceChar[index + 2] == unicode_prefix && sourceChar[index + 3] == separator) { int length = Integer.parseInt(String.valueOf(sourceChar[index + 1]));
char[] hexchars = new char[length]; // 创建⼀个4⾄六位的数组,来存储uncode码的HEX值
for (int j = 0; j < length; j++) {
char ch = sourceChar[index + 4 + j];// 4位识别码
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')) {
hexchars[j] = ch;
} else { // 字符范围不对
sb.append(sourceChar[index]);
index++;
break;
}
}
sb.Chars(Integer.parseInt(new String(hexchars), 16)));
index += (4 + length);// 4位前缀+4-6位字符码
} else if (sourceChar[index + 1] == unicode_prefix) { // 通⽤字符的反转
// 因为第⼆平⾯之上的,已经采⽤了我们⾃⼰转码格式,所以这⾥是固定的长度4
char[] hexchars = new char[4];
for (int j = 0; j < 4; j++) {
char ch = sourceChar[index + 2 + j]; // 两位识别码要去掉
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')) {
hexchars[j] = ch; // 4位识别码
} else { // 字符范围不对
sb.append(sourceChar[index]);
index++;
break;
}
sb.Chars(Integer.parseInt(String.valueOf(hexchars), 16)));
index += (2 + 4);// 2位前缀+4位字符码
}
} else {
sb.append(sourceChar[index]);
index++;
continue;
}
} else {
sb.append(sourceChar[index]);
index++;
continue;
}
}
String();
}
public static String filter(String src) {
if (src == null) {
return null;
}
int cpCount = dePointCount(0, src.length());
int firCodeIndex = src.offsetByCodePoints(0, 0);
int lstCodeIndex = src.offsetByCodePoints(0, cpCount - 1);
StringBuilder sb = new StringBuilder(src.length());
for (int index = firCodeIndex; index <= lstCodeIndex;) {
int codepoint = dePointAt(index);
if (!isEmojiCharacter(codepoint)) {
}
index += ((Character.isSupplementaryCodePoint(codepoint)) ? 2 : 1);        }
String();
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。