前言
開發(fā)過程中驼侠,遇到了帶有Emoji表情的字符串無法存入后臺(tái)數(shù)據(jù)庫(kù)問題姿鸿。原因是Mysql的utf8編碼最多3個(gè)字節(jié),而Emoji表情或者某些特殊字符是4個(gè)字節(jié)倒源,所以數(shù)據(jù)插不進(jìn)去苛预。這種問題應(yīng)該交給后臺(tái)處理,如果后臺(tái)不處理笋熬,則ios和Andriod要統(tǒng)一對(duì)emoji表情編碼热某,這里采用UTF-8編碼,在解決這個(gè)問題之前胳螟,先對(duì)以下相關(guān)知識(shí)做個(gè)了解昔馋。
相關(guān)知識(shí)
1.位、字節(jié)糖耸、字符的概念和區(qū)別秘遏。
位(bit): 數(shù)據(jù)存儲(chǔ)的最小單位,每個(gè)二進(jìn)制數(shù)字0或者1就是1個(gè)
位嘉竟,比如 11010101是一個(gè)八位二進(jìn)制數(shù)邦危。
字節(jié)(byte):8個(gè)位構(gòu)成一個(gè)字節(jié);即:1 byte (字節(jié))= 8 bit(位)舍扰;1 KB = 1024 B(字節(jié))倦蚪;
字符: 是指計(jì)算機(jī)中使用的字母、數(shù)字边苹、字和符號(hào)陵且,也就是用戶能看到的。
字符集: 各種各個(gè)字符的集合勾给,也就是某些漢字滩报、字母(A、b播急、c)和符號(hào)(空格脓钾、引號(hào)..)會(huì)被收入標(biāo)準(zhǔn)中;例如ASCii字符集桩警,gb2312字符集可训,Unicode字符集等。
編碼: 規(guī)定每個(gè)“字符”分別用一個(gè)字節(jié)還是多個(gè)字節(jié)存儲(chǔ)捶枢,用哪些字節(jié)來存儲(chǔ)握截,這個(gè)規(guī)定就叫做“編碼”。通俗來講烂叔,編碼就是按照規(guī)則對(duì)字符進(jìn)行翻譯成對(duì)應(yīng)的二進(jìn)制數(shù)谨胞,在計(jì)算器中運(yùn)行存儲(chǔ),用戶看的時(shí)候蒜鸡,再解碼顯示成用戶能看得懂的胯努。
2.UTF-8編碼中,字節(jié)和字符的對(duì)應(yīng)關(guān)系逢防。
1個(gè)英文字符 和 英文標(biāo)點(diǎn) = 1個(gè)字節(jié)
1個(gè)中文(含繁體) 和 中文標(biāo)點(diǎn) = 3個(gè)字節(jié)
Emoji表情 和 某些特殊字符 = 4個(gè)字節(jié)
3.String類的length() 和 codePointCount() 的區(qū)別
length():返回的是使用的UTF-16編碼的字符代碼單元數(shù)量叶沛,不一定是我們認(rèn)為的字符個(gè)數(shù)。
codePointCount():返回的是代碼點(diǎn)數(shù)量忘朝,是實(shí)際的字符個(gè)數(shù)灰署。
例如1個(gè)Emoji表情是一個(gè)字符,codePointCount()是1局嘁,但是length()是2溉箕。
原因:
常用的UniCode字符使用一個(gè)代碼單元就可以表示(如英文、數(shù)字悦昵、中文)约巷,但有些輔助字符需要一對(duì)代碼單元表示(如Emoji表情)。length()計(jì)算的是代碼單元的數(shù)量旱捧,codePointCount()計(jì)算的是代碼點(diǎn)數(shù)独郎。
補(bǔ)充:
有的emoji表情codePointCount()是2,length()也是2枚赡,因?yàn)樗鼈兛赡軘y帶著一個(gè)我們看不到的符號(hào)氓癌,代碼打印能看到,這可能就涉及到是否增補(bǔ)字符的問題了贫橙。
實(shí)例
對(duì)含有emoji表情的字符串進(jìn)行UTF-8編解碼
對(duì)整段字符進(jìn)行編碼
這種方式簡(jiǎn)單便捷贪婉,如無特殊情況,這么處理就足夠了卢肃。最后顯示的都是帶%號(hào)的字符串疲迂,例如:%E5%93%88
try {
//編碼
String encodeStr = URLEncoder.encode(src, "UTF-8");
//解碼
String decodeStr = URLDecoder.decode(encodeStr, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
對(duì)Emoij表情單獨(dú)編碼
如果遇到特殊要求才顿,只對(duì)emoji表情編碼,其他字符不編碼尤蒿,那就要用到codePointCount()了郑气,解碼的時(shí)候直接對(duì)整段解碼即可。
public class EmojiUtils {
/**
* 對(duì)emoji表情單獨(dú)編碼
* @param src
* @return
*/
public static String escape(String src){
//1.得到代碼點(diǎn)數(shù)量腰池,也即是實(shí)際字符數(shù)尾组,注意和length()的區(qū)別
//舉例:
//一個(gè)emoji表情是一個(gè)字符,codePointCount()是1示弓,length()是2讳侨。
//但是遇到過一個(gè)emoji表情居然帶著空格符號(hào),結(jié)果它的codePointCount()是2奏属,length()也是2跨跨,實(shí)際上單獨(dú)拎emoji來說,它的length()應(yīng)該說是1才對(duì)了囱皿。
//推測(cè)這就是它們是否屬于增補(bǔ)字符范圍內(nèi)歹叮,length=2的是增補(bǔ)字符,length=1的不是铆帽。
int cpCount = src.codePointCount(0, src.length());
//2.得到字符串的第一個(gè)代碼點(diǎn)index咆耿,和最后一個(gè)代碼點(diǎn)index
//舉例:比如3個(gè)emoji表情,那么它的cpCount=3爹橱;firCodeIndex=0萨螺;lstCodeIndex=4
//因?yàn)槊總€(gè)emoji表情length()是2,所以第一個(gè)是0-1愧驱,第二個(gè)是2-3慰技,第三個(gè)是4-5
int firCodeIndex = src.offsetByCodePoints(0, 0);
int lstCodeIndex = src.offsetByCodePoints(0, cpCount - 1);
StringBuilder sb = new StringBuilder();
int index = firCodeIndex;
while (index<=lstCodeIndex){
//3.獲得代碼點(diǎn),判斷是否是emoji表情
//注意组砚,codePointAt(int) 這個(gè)int對(duì)應(yīng)的是codeIndex
//舉例:3個(gè)emoji表情吻商,取第3個(gè)emoji表情,index應(yīng)該是4
int codepoint = src.codePointAt(index);
if (!isEmojiCharacter(codepoint)) {
sb.append((char) codepoint);
}else{
try {
//4.對(duì)emoji表情UTF-8編碼糟红,判斷是否是增補(bǔ)字符范圍內(nèi)
int length = Character.isSupplementaryCodePoint(codepoint)? 2 : 1;
String encoderStr = URLEncoder.encode(src.substring(index,index + length), "UTF-8");
sb.append(encoderStr);
} catch (UnsupportedEncodingException e) {
}
}
//4.確定指定字符(Unicode代碼點(diǎn))是否在增補(bǔ)字符范圍內(nèi)艾帐。
//因?yàn)槌吮砬椋€有些特殊字符也是在增補(bǔ)字符方位內(nèi)的盆偿。
index += Character.isSupplementaryCodePoint(codepoint)? 2 : 1;
}
return sb.toString();
}
/**
* 過濾掉emoji表情
* @param src
* @return
*/
public static String filter(String src) {
if (src == null) {
return null;
}
//得到codePointCount
int cpCount = src.codePointCount(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;) {
//遍歷每個(gè)codePoint
int codepoint = src.codePointAt(index);
if (!isEmojiCharacter(codepoint)) {
System.err.println("codepoint:" + Integer.toHexString(codepoint));
sb.append((char) codepoint);
}
index += ((Character.isSupplementaryCodePoint(codepoint)) ? 2 : 1);
}
return sb.toString();
}
/**
* 是否是Emoji表情
* @param codePoint
* @return
*/
private static boolean isEmojiCharacter(int codePoint) {
return (codePoint >= 0x2600 && codePoint <= 0x27BF) // 雜項(xiàng)符號(hào)與符號(hào)字體
|| codePoint == 0x303D || codePoint == 0x2049 || codePoint == 0x203C
|| (codePoint >= 0x2000 && codePoint <= 0x200F)//
|| (codePoint >= 0x2028 && codePoint <= 0x202F)//
|| codePoint == 0x205F //
|| (codePoint >= 0x2065 && codePoint <= 0x206F)//
/* 標(biāo)點(diǎn)符號(hào)占用區(qū)域 */
|| (codePoint >= 0x2100 && codePoint <= 0x214F)// 字母符號(hào)
|| (codePoint >= 0x2300 && codePoint <= 0x23FF)// 各種技術(shù)符號(hào)
|| (codePoint >= 0x2B00 && codePoint <= 0x2BFF)// 箭頭A
|| (codePoint >= 0x2900 && codePoint <= 0x297F)// 箭頭B
|| (codePoint >= 0x3200 && codePoint <= 0x32FF)// 中文符號(hào)
|| (codePoint >= 0xD800 && codePoint <= 0xDFFF)// 高低位替代符保留區(qū)域
|| (codePoint >= 0xE000 && codePoint <= 0xF8FF)// 私有保留區(qū)域
|| (codePoint >= 0xFE00 && codePoint <= 0xFE0F)// 變異選擇器
|| codePoint >= 0x10000; // Plane在第二平面以上的柒爸,char都不可以存,全部都轉(zhuǎn)
}
}
參考
https://blog.csdn.net/u014166319/article/details/71308112
https://www.cnblogs.com/mojxtang/p/10154907.html