JAVA 查找并移除字符串中的Emoji ??

一周叮、基礎(chǔ)知識(shí)

  1. Emoji 實(shí)際上是 UTF-8 (Unicode) 字符集上的特殊字符运授,多數(shù)基本 Emoji 都被分配到 Unicode 編碼表 1 號(hào)平面的 U+1F300–1F6FF 和 U+1F900–1FAFF 兩個(gè)區(qū)域,由2個(gè)字符組成董瞻。

  2. 膚色修飾:大多數(shù)與人相關(guān)的 Emoji 默認(rèn)是黃色的,所以后來引入了五個(gè)新碼點(diǎn)作為修飾符:U+1F3FB江锨、U+1F3FCU+1F3FDU+1F3FE酱塔、U+1F3FF。膚色修飾符追加到現(xiàn)有的 Emoji 后形成新的樣式:
    U+1F44B(?? ) + U+1F3FD= ????

  3. 符號(hào)變體或組合:一個(gè)普通的字后連接一個(gè)或多個(gè)變體危虱、組合標(biāo)識(shí)(字符)羊娃,組合形成的 Emoji :
    U+25C0+U+FE0F= ??
    U+27A1+U+FE0F= ??
    1+U+FE0F+U+20E3= 1??

  4. 國旗:每個(gè)國旗由2個(gè)地區(qū)標(biāo)識(shí)符組合而成,地區(qū)標(biāo)識(shí)符的對(duì)應(yīng)碼點(diǎn)范圍為U+1F1E6~U+1F1FF埃跷,等同于2個(gè)指定范圍的普通 Emoji 字符組成蕊玷。
    U+1F1E8+U+1F1F3= ????

  5. 零寬度連接符(ZWJ):多個(gè)基礎(chǔ) Emoji 通過零寬度連接符(U+200D)形成的復(fù)雜 Emoji:
    ??+U+200D+??= ????
    ??+U+200D+??+U+200D+??= ??????
    ??+U+200D+??+U+200D+??+U+200D+??= ????????

  6. 序列:一個(gè)基礎(chǔ) Emoji 加上多個(gè)標(biāo)簽字符 (U+E0020~ U+E007F )并以 Tag Cancel(U+E007)結(jié)尾,組合形成一個(gè)復(fù)雜 Emoji:
    U+1F3F4(??) +U+E0067+U+E0062+U+E0065+U+E006E+U+E0067+U+E007F= ??

  7. 特殊符號(hào):
    特殊符號(hào)只有1個(gè)字符捌蚊,有些符號(hào)在某些環(huán)境下會(huì)被當(dāng)做Emoj處理:?集畅、?、?;

Unicode 只是約定了碼點(diǎn)到 emoji 的映射關(guān)系缅糟,并沒有約定 Emoji 圖形挺智,每個(gè) Emoji 字體文件可以按照自己的想法設(shè)計(jì) Emoji。

二窗宦、解決方案

  1. 除了一些特殊符號(hào)形式的 Emoji赦颇,其他Emoji至少有2個(gè)字符,所以先根據(jù)第二個(gè)字符類型判斷是否為Emoji赴涵,使用Character.UnicodeBlock.ofCharacter.getType方法判定每個(gè)字符的類型媒怯。
  2. 通過第二個(gè)字符類型判斷當(dāng)前2個(gè)字符為 Emoji 后:
    1)判斷是否有后續(xù)修飾
    2)判斷處理國旗類型;判斷處理膚色修飾髓窜;判斷處理 Emoji 序列標(biāo)簽扇苞;判斷處理零寬度連接符;判斷處理連續(xù)變體寄纵、組合標(biāo)識(shí)鳖敷;按照普通 Emoji 處理;
  3. 處理單字符的特殊符號(hào)程拭,這一類型內(nèi)有的屬于 Emoji定踱,有的不是,目前全部簡(jiǎn)單的按照普通 Emoji 處理恃鞋;

三崖媚、完整代碼

package com.zpf.tool;

import java.util.List;

public class EmojiUtil {

    public static boolean isEmojiNationalFlag(int codePoint) {
        return codePoint >= 127462 && codePoint <= 127487;
    }

    // String str = new String(new int[]{0x1F44B, 0x1F3FD}, 0, 2);
    public static boolean isEmojiSkinColor(int codePoint) {
        return codePoint >= 127995 && codePoint <= 127999;
    }

    // String str = new String(new int[]{0x1F3F4, 0xE0067, 0xE0062, 0xE0065, 0xE006E, 0xE0067, 0xE007F}, 0, 7);
    public static boolean isEmojiTagEnd(int codePoint) {
        return codePoint == 917631;
    }

    public static boolean isEmojiTagSpec(int codePoint) {
        return codePoint >= 917536 && codePoint <= 917630;
    }

    public static boolean isEmojiDecorateBlock(Character.UnicodeBlock block) {
        if (block == null) {
            return false;
        }
        return block.equals(Character.UnicodeBlock.VARIATION_SELECTORS)
                || block.equals(Character.UnicodeBlock.VARIATION_SELECTORS_SUPPLEMENT)
                || block.equals(Character.UnicodeBlock.COMBINING_HALF_MARKS)
                || block.equals(Character.UnicodeBlock.COMBINING_MARKS_FOR_SYMBOLS)
                || block.equals(Character.UnicodeBlock.COMBINING_DIACRITICAL_MARKS)
                || block.equals(Character.UnicodeBlock.COMBINING_DIACRITICAL_MARKS_SUPPLEMENT);
    }

    public static void pickAllEmoji(CharSequence data, StringBuilder removeResult, List<String> emojiList) {
        if (removeResult == null && emojiList == null) {
            return;
        }
        if (removeResult != null) {
            removeResult.delete(0, removeResult.length());
        }
        if (emojiList != null) {
            emojiList.clear();
        }
        if (data == null || data.length() == 0) {
            return;
        }
        StringBuilder emojiBuilder = new StringBuilder();
        int i = 0;
        int j;
        Character.UnicodeBlock block;
        while (i < data.length()) {
            if (i + 1 < data.length()) {
                block = Character.UnicodeBlock.of(data.charAt(i + 1));
                if (isEmojiDecorateBlock(block) || Character.UnicodeBlock.LOW_SURROGATES.equals(block)) {
                    if (i + 2 >= data.length()) {
                        emojiBuilder.append(data, i, i + 2);
                        break;
                    }
                    j = handleNationalFlag(data, i, emojiBuilder, emojiList);
                    if (i != j) {
                        i = j;
                        continue;
                    }
                    j = handleHumanSkin(data, i, emojiBuilder, emojiList);
                    if (i != j) {
                        i = j;
                        continue;
                    }
                    j = handleTagSequence(data, i, emojiBuilder, emojiList);
                    if (i != j) {
                        i = j;
                        continue;
                    }
                    emojiBuilder.append(data, i, i + 2);
                    i = handleNextChar(data, i + 2, emojiBuilder, emojiList);
                    continue;
                }
            }
            recordEmoji(emojiBuilder, emojiList);
            int type = Character.getType(data.charAt(i));
            if (type == (int) Character.OTHER_SYMBOL) {//特殊符號(hào)一律按照Emoji處理
                if (emojiList != null) {
                    emojiList.add(String.valueOf(data.charAt(i)));
                }
            } else if (removeResult != null) {
                removeResult.append(data.charAt(i));
            }
            i++;
        }
        recordEmoji(emojiBuilder, emojiList);
    }

    private static int handleNextChar(CharSequence data, int i, StringBuilder emojiBuilder, List<String> emojiList) {
        if (i >= data.length()) {
            return i;
        }
        char nextChar = data.charAt(i);
        if (nextChar == '\u200D') {//零寬度連接符
            emojiBuilder.append(nextChar);
            return i + 1;
        }
        int j = i;
        Character.UnicodeBlock block;
        while (j < data.length()) {
            nextChar = data.charAt(j);
            block = Character.UnicodeBlock.of(nextChar);
            if (isEmojiDecorateBlock(block)) {
                emojiBuilder.append(nextChar);
                j++;
            } else {
                break;
            }
        }
        if (i != j) {
            recordEmoji(emojiBuilder, emojiList);
        }
        return j;
    }

    private static int handleNationalFlag(CharSequence data, int i, StringBuilder emojiBuilder, List<String> emojiList) {
        int codePoint = Character.codePointAt(data, i);
        if (isEmojiNationalFlag(codePoint)) {//處理國旗類型
            recordEmoji(emojiBuilder, emojiList);//提交未處理
            if (i + 3 < data.length()) {
                codePoint = Character.codePointAt(data, i + 2);
                if (isEmojiNationalFlag(codePoint)) {
                    emojiBuilder.append(data, i, i + 4);
                    recordEmoji(emojiBuilder, emojiList);
                    i = i + 4;
                }
            }
            i = i + 2;
        }
        return i;
    }

    private static int handleHumanSkin(CharSequence data, int i, StringBuilder emojiBuilder, List<String> emojiList) {
        if (i + 3 >= data.length()) {
            return i;
        }
        int codePoint = Character.codePointAt(data, i + 2);
        if (isEmojiSkinColor(codePoint)) {//膚色修飾
            emojiBuilder.append(data, i, i + 4);
            recordEmoji(emojiBuilder, emojiList);
            i = i + 4;
        }
        return i;
    }

    private static int handleTagSequence(CharSequence data, int i, StringBuilder emojiBuilder, List<String> emojiList) {
        if (i + 3 >= data.length()) {
            return i;
        }
        int codePoint = Character.codePointAt(data, i + 2);
        if (isEmojiTagSpec(codePoint)) {
            emojiBuilder.append(data, i, i + 4);
            i = i + 4;
            while (i < data.length()) {
                codePoint = Character.codePointAt(data, i);
                if (isEmojiTagSpec(codePoint)) {
                    emojiBuilder.append(data, i, i + 2);
                    i = i + 2;
                } else if (isEmojiTagEnd(codePoint)) {
                    emojiBuilder.append(data, i, i + 2);
                    recordEmoji(emojiBuilder, emojiList);
                    i = i + 2;
                    break;
                } else { //error
                    break;
                }
            }
            emojiBuilder.delete(0, emojiBuilder.length());
        } else if (isEmojiTagEnd(codePoint)) {
            emojiBuilder.append(data, i, i + 4);
            recordEmoji(emojiBuilder, emojiList);
            i = i + 4;
        }
        return i;
    }

    private static void recordEmoji(StringBuilder builder, List<String> emojiList) {
        if (builder != null && builder.length() > 0) {
            if (emojiList != null) {
                emojiList.add(builder.toString());
            }
            builder.delete(0, builder.length());
        }
    }

}

2024.03.13

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恤浪,隨后出現(xiàn)的幾起案子畅哑,更是在濱河造成了極大的恐慌,老刑警劉巖水由,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敢课,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绷杜,警方通過查閱死者的電腦和手機(jī)直秆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鞭盟,“玉大人圾结,你說我怎么就攤上這事〕菟撸” “怎么了筝野?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長粤剧。 經(jīng)常有香客問我歇竟,道長,這世上最難降的妖魔是什么抵恋? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任焕议,我火速辦了婚禮,結(jié)果婚禮上弧关,老公的妹妹穿的比我還像新娘盅安。我一直安慰自己,他們只是感情好世囊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布别瞭。 她就那樣靜靜地躺著,像睡著了一般株憾。 火紅的嫁衣襯著肌膚如雪蝙寨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天嗤瞎,我揣著相機(jī)與錄音墙歪,去河邊找鬼。 笑死猫胁,一個(gè)胖子當(dāng)著我的面吹牛箱亿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弃秆,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼届惋,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了菠赚?” 一聲冷哼從身側(cè)響起脑豹,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衡查,沒想到半個(gè)月后瘩欺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年俱饿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歌粥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拍埠,死狀恐怖失驶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枣购,我是刑警寧澤嬉探,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站棉圈,受9級(jí)特大地震影響涩堤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜分瘾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一胎围、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芹敌,春花似錦痊远、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至液茎,卻和暖如春逞姿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捆等。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工滞造, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栋烤。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓谒养,卻偏偏與公主長得像,于是被迫代替她去往敵國和親明郭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子买窟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容