Java處理emoji

MojxTang發表於2018-12-21

1.問題產生情況

我遇到這個問題是做微信開發的時候有些有用的頭像用了微信的emoji表情,然而我的mysql資料庫用的編碼是utf8_general_ci,就是utf-8編碼,結果也就報錯誤了。

 

2.為什麼會出現這種原因

因為mysql的utf8編碼的一個字元最多3個位元組,但是一個emoji表情為4個位元組,所以utf8不支援儲存emoji表情。但是utf8的超集utf8mb4一個字元最多能有4位元組,所以能支援emoji表情的儲存。

 

3.解決方法之一

把你的資料庫編碼集設定為utf8mb4,無論是資料庫還是表,還是欄位。雖然會增加儲存,但是這個可以忽略不計。

 

4.解決方法之二

有句話說得好,問題來了要麼解決要麼折中解決。如果有些原因你不能修改資料庫編碼之類的,你可以用java的一些外掛,如emoji-java這種emoji表情外掛對錶情進行特殊處理,然後儲存或者去掉表情,這也是一種解決方法哦。

 

最後來段程式碼

package com.mojxtang;

import org.apache.commons.lang3.StringUtils;

/**
 * <pre>
 * 本類的主要功能是將帶有emoji的字串,格式化成unicode字串,並且提供可見unicode字元反解成emoji字元
 *  
 * 
 * 相關識知點:
 * <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)
 * 
 * 參考:
 * 百度百科: https://baike.baidu.com/item/emoji/8154456?fr=aladdin
*emoji表情:http://www.fhdq.net/emoji/emojifuhao.html
 * 雜項象形符號: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>
 */
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 (StringUtils.isBlank(src)) {
			return src;
		}
		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; index++) {
			int codepoint = src.codePointAt(index);
			if (isEmojiCharacter(codepoint)) {
				String hash = Integer.toHexString(codepoint);
				sb.append(unicode_separator).append(hash.length()).append(unicode_prefix).append(separator)
						.append(hash);
				// hash 長度,4位1個位元組
				index += (hash.length() - 1) / 4;
			} else {
				sb.append((char) codepoint);
			}
		}
		return sb.toString();
	}

	/** 解析可見字元標識字串 */
	public static String reverse(String src) {
		// 查詢對應編碼的標識位
		if (StringUtils.isBlank(src)) {
			return src;
		}
		StringBuilder sb = new StringBuilder(src.length());
		char[] sourceChar = src.toCharArray();
		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.append(Character.toChars(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.append(Character.toChars(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;
			}
		}

		return sb.toString();
	}

	public static String filter(String src) {
		if (src == null) {
			return null;
		}
		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;) {
			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();
	}
}

  部落格地址:Java處理emoji

相關文章