提取字符串中所有表情符号的正则表达式是什么?

72
我有一个以UTF-8编码的字符串。例如:
That's a nice joke  

我需要提取句子中的所有表情符号。而且这些表情符号可以是任何东西。
当在终端中使用命令less text.txt查看这个句子时,它会显示为:
That's a nice joke <U+1F606><U+1F606><U+1F606> <U+1F61B>

这是表情符号的对应UTF代码。所有表情符号的代码可以在emojitracker找到。
为了找到所有出现的情况,我使用了一个正则表达式模式(<U\+\w+?>),但对于UTF-8编码的字符串却没有起作用。
以下是我的代码:
String s = "That's a nice joke  ";
Pattern pattern = Pattern.compile("(<U\\+\\w+?>)");
Matcher matcher = pattern.matcher(s);
List<String> matchList = new ArrayList<>();
while (matcher.find()) {
    matchList.add(matcher.group());
}
for (int i = 0; i < matchList.size(); i++) {
    System.out.println(matchList.get(i));
}

这个 pdf范围:1F300–1F5FF 适用于杂项符号和象形文字。所以我想捕获在这个范围内的任何字符。

2
那个<U+1F606>字符串是特定于less的 - 此外,您的解决方案想法也会捕获几乎任何其他Unicode字符。唯一真正的解决方案是拥有所有与表情符号相对应的Unicode代码点列表。 - Drew McGowen
1
你需要找到所有你想要的表情符号(代码点)的列表,它们分布在许多不同的Unicode块中。这个PDF文件有一个“好的样本”(根据第一个链接)... - T.J. Crowder
1
我来这里是想找一个正则表达式,可以将其粘贴到Sublime Text中以查找表情符号。可惜没有找到。 - adib
你可以使用Character类 https://dev59.com/MF4c5IYBdhLWcg3wD2rC#41147459 - user2474486
@vishalaksh 我脑海中浮现出一个问题——“你为什么需要那个?”我的意思是,它有什么用处呢?谢谢! - eRaisedToX
显示剩余2条评论
19个回答

54

使用emoji-java,我编写了一个简单的方法,可以删除所有表情符号,包括Fitzpatrick修饰符。虽然需要使用外部库,但比那些笨重的正则表达式更易于维护。

使用方法:

String input = "A string with a \uD83D\uDC66\uD83C\uDFFFfew emojis!";
String result = EmojiParser.removeAllEmojis(input);

emoji-java 的 Maven 安装:

<dependency>
  <groupId>com.vdurmont</groupId>
  <artifactId>emoji-java</artifactId>
  <version>3.1.3</version>
</dependency>

Gradle:

implementation 'com.vdurmont:emoji-java:3.1.3'

编辑:之前提交的答案已被纳入emoji-java源代码中。


4
我喜欢这样的回答。它起到了很好的效果。谢谢! - TheKingInTheNorth
我也使用了这个库来删除表情符号,它完美地运行了。不过有一点需要注意,代码片段已经过时,并且在最新版本中无法正常工作(抛出了一些模式异常)。文档建议使用EmojiParser#removeAllEmojis(String),确实可以顺利地运行。 - Yonatan Wilkof
如果您正在使用此功能,这是一个jar的链接:https://github.com/vdurmont/emoji-java/releases,这是一个依赖项的链接:http://mvnrepository.com/artifact/org.json/json/20080701。 - Whitecat
1
@gidim,请将依赖项的版本更新为3.1.3。您列出的2.0.1版本没有EmojiParser.removeAllEmojis(String input)。除此之外,非常感谢您提供如此优秀的库! - Bruno Carrier
1
@BrunoCarrier 谢谢!已更新。顺便说一下,我不是这个库的作者。我只是写了一个表情符号删除函数。 - gidim
@ gidim,不幸的是,这并没有删除像(Mahjong Tile Plum)这样的字符。有什么原因吗? - azizbekian

38

您刚提到的pdf显示杂项符号和象形文字的范围为1F300-1F5FF。假设我想捕获位于此范围内的任何字符。现在该怎么办呢?

好的,但是我要注意一下,你问题中的表情符号超出了那个范围! :-)

这些字符超过了0xFFFF,这使事情变得复杂,因为Java字符串存储UTF-16。所以我们不能仅仅使用一个简单的字符类来处理它。我们将会有一对代理项。(更多信息:http://www.unicode.org/faq/utf_bom.html

在UTF-16中,U+1F300最终成为一对\uD83C\uDF00;U+1F5FF最终成为\uD83D\uDDFF。请注意,第一个字符上涨了,我们越过了至少一个边界。因此,我们必须知道我们正在寻找哪些代理项范围。

由于我不熟悉UTF-16的内部工作原理,因此我编写了一个程序来进行探索(源代码在最后 - 如果我是您的话,我会反复检查一遍,而不是相信我)。它告诉我,我们正在寻找\uD83C后跟范围\uDF00-\uDFFF(包括这两个字符)中的任何东西,或\uD83D后跟范围\uDC00-\uDDFF(包括这两个字符)中的任何东西。

因此,理论上,我们现在可以编写一个模式:

// This is wrong, keep reading
Pattern p = Pattern.compile("(?:\uD83C[\uDF00-\uDFFF])|(?:\uD83D[\uDC00-\uDDFF])");

这是两个非捕获组的交替,第一组用于以 \uD83C 开头的情况,第二组用于以 \uD83D 开头的情况。

但是匹配失败(没有找到任何内容)。我相当确定这是因为我们尝试在各种地方指定了一个代理对中的一半

Pattern p = Pattern.compile("(?:\uD83C[\uDF00-\uDFFF])|(?:\uD83D[\uDC00-\uDDFF])");
// Half of a pair --------------^------^------^-----------^------^------^

我们不能简单地将代理对拆分开来,它们被称为代理是有原因的。 :-)

因此,我认为我们无法使用正则表达式(或任何基于字符串的方法)来解决这个问题。 我认为我们必须通过搜索char数组来解决。

char数组包含UTF-16值,因此如果我们用艰难的方式查找,就可以找到这些半对。

String s = new StringBuilder()
                .append("Thats a nice joke ")
                .appendCodePoint(0x1F606)
                .appendCodePoint(0x1F606)
                .appendCodePoint(0x1F606)
                .append(" ")
                .appendCodePoint(0x1F61B)
                .toString();
char[] chars = s.toCharArray();
int index;
char ch1;
char ch2;

index = 0;
while (index < chars.length - 1) { // -1 because we're looking for two-char-long things
    ch1 = chars[index];
    if ((int)ch1 == 0xD83C) {
        ch2 = chars[index+1];
        if ((int)ch2 >= 0xDF00 && (int)ch2 <= 0xDFFF) {
            System.out.println("Found emoji at index " + index);
            index += 2;
            continue;
        }
    }
    else if ((int)ch1 == 0xD83D) {
        ch2 = chars[index+1];
        if ((int)ch2 >= 0xDC00 && (int)ch2 <= 0xDDFF) {
            System.out.println("Found emoji at index " + index);
            index += 2;
            continue;
        }
    }
    ++index;
}

显然那只是调试级别的代码,但它完成了工作。(在您提供的带有表情符号的字符串中,当然不会找到任何内容,因为它们超出了范围。但是如果您将第二对的上限更改为0xDEFF而不是0xDDFF,那么就可以找到了。不知道这是否还包括非表情符号。)


我编写程序以查找代理范围的来源:

public class FindRanges {

    public static void main(String[] args) {
        char last0 = '\0';
        char last1 = '\0';
        for (int x = 0x1F300; x <= 0x1F5FF; ++x) {
            char[] chars = new StringBuilder().appendCodePoint(x).toString().toCharArray();
            if (chars[0] != last0) {
                if (last0 != '\0') {
                    System.out.println("-\\u" + Integer.toHexString((int)last1).toUpperCase());
                }
                System.out.print("\\u" + Integer.toHexString((int)chars[0]).toUpperCase() + " \\u" + Integer.toHexString((int)chars[1]).toUpperCase());
                last0 = chars[0];
            }
            last1 = chars[1];
        }
        if (last0 != '\0') {
            System.out.println("-\\u" + Integer.toHexString((int)last1).toUpperCase());
        }
    }
}

输出:

\uD83C \uDF00-\uDFFF
\uD83D \uDC00-\uDDFF

@purrrminator:请查看关于范围的注释。上面只是处理特定范围的示例,但我警告OP还有其他范围。 - T.J. Crowder

22

这个没有为我找到表情符号。 - Gandalf458
@Gandalf458 我更新了我的答案,并添加了一个样例的截图。 - Desgard_Duan
它似乎在Java中可以工作,但在C#中不行。我猜C#不把表情符号视为Other_Symbol。 - Gandalf458
在Presto([prestodb])上对我有效,它通常接受Java模式语法(https://prestodb.io/docs/current/functions/regexp.html)。 - MichaelChirico

21

我有类似的问题。以下内容对我很有帮助,而且与代理对匹配。

public class SplitByUnicode {
    public static void main(String[] argv) throws Exception {
        String string = "Thats a nice joke  ";
        System.out.println("Original String:"+string);
        String regexPattern = "[\uD83C-\uDBFF\uDC00-\uDFFF]+";
        byte[] utf8 = string.getBytes("UTF-8");

        String string1 = new String(utf8, "UTF-8");

        Pattern pattern = Pattern.compile(regexPattern);
        Matcher matcher = pattern.matcher(string1);
        List<String> matchList = new ArrayList<String>();

        while (matcher.find()) {
            matchList.add(matcher.group());
        }

        for(int i=0;i<matchList.size();i++){
            System.out.println(i+":"+matchList.get(i));

        }
    }
}

输出为:


原始字符串:Thats a nice joke
0:
1:

https://dev59.com/g4Dba4cB1Zd3GeqPDWG4#24071599中找到了正则表达式。


这似乎很顺利,而且也很简单,只要去掉示例Java样板即可。 - r3flss ExlUtr
这个样板代码只是为了完整性,如果有Java的新手想要测试它 :) - Karan Ashar
1
我尝试使用[\uD83C-\uDBFF\uDC00-\uDFFF]+来删除表情符号,但它也删除了下一个字符-。 最终我使用了[\uD800\uDC00-\uDBFF\uDFFF] - mgershen

12

这个方法适用于我的Java 8版本:

public static String mysqlSafe(String input) {
  if (input == null) return null;
    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < input.length(); i++) {
      if (i < (input.length() - 1)) { // Emojis are two characters long in java, e.g. a rocket emoji is "\uD83D\uDE80";
        if (Character.isSurrogatePair(input.charAt(i), input.charAt(i + 1))) {
          i += 1; //also skip the second character of the emoji
          continue;
        }
      }
      sb.append(input.charAt(i));
    }

  return sb.toString();
}

非常感谢!您指引了我所需的正确方向。 - HannahCarney
1
这个逻辑只是简单地跳过BMP之外的代码点。在某些情况下,这可能看起来没问题,但并不总是有效。首先,这不会过滤掉位于dingbet块中的表情符号,其次,这甚至会过滤掉一些罕见的字母。 - Jenix

9
你可以像这样做。
    String s="Thats a nice joke  ";
    Pattern pattern = Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]",
                                      Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher(s);
    List<String> matchList = new ArrayList<String>();

    while (matcher.find()) {
        matchList.add(matcher.group());
    }

    for(int i=0;i<matchList.size();i++){
        System.out.println(matchList.get(i));
    }

7
提取所有表情符号的最佳正则表达式如下:
(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c[\ude32-\ude3a]|[\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])

它可以识别许多其他答案未考虑的单个字符表情符号。关于这个正则表达式是如何工作的更多信息,请查看此帖子。https://medium.com/@thekevinscott/emojis-in-javascript-f693d0eb79fb


当我将此内容输入到 Pattern.compile() 方法中时,会出现错误 Unclosed character class near index 657 - Jack Cole

6
这个棘手的问题有两种解决方法。
第一种方式是使用第三方库,如emoji-java和emoji4j。这些在上文中提到。您可以轻松使用containsEmojiremovesEmoji等方法。在您自己的应用程序中,您需要保持与这些库的更新。
至于我,我想找到一个简单的解决方案来解决这个问题。
经过一整天的搜索,我找到了一个神奇的正则表达式: "(?:[\uD83C\uDF00-\uD83D\uDDFF]|[\uD83E\uDD00-\uD83E\uDDFF]|[\uD83D\uDE00-\uD83D\uDE4F]|[\uD83D\uDE80-\uD83D\uDEFF]|[\u2600-\u26FF]\uFE0F?|[\u2700-\u27BF]\uFE0F?|\u24C2\uFE0F?|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?|[\u2934\u2935]\uFE0F?|[\u3030\u303D]\uFE0F?|[\u3297\u3299]\uFE0F?|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?|[\u203C\u2049]\uFE0F?|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?|[\u00A9\u00AE]\uFE0F?|[\u2122\u2139]\uFE0F?|\uD83C\uDC04\uFE0F?|\uD83C\uDCCF\uFE0F?|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?)" 我已经在Java中测试过,完美地解决了我的问题。
您可以在Github页面上查看此内容:

https://github.com/zly394/EmojiRegex

注意事项:

@Eric Nakagawa提供的答案存在一些错误,无法正常操作。


这不仅可以捕获表情符号,如果你在Big List of Naughty Strings上使用它,你会得到很多非表情符号的匹配。 - Jack Cole

5
假设您正在询问标准Unicode表情符号范围(不同供应商有不同的块),您可以考虑以下三个范围:
  • 0x20a0 - 0x32ff
  • 0x1f000 - 0x1ffff
  • 0xfe4e5 - 0xfe4ee
除了T.J.Crowder分享给我们的所有周到解释之外,需要注意的是,从Java 7开始,可以轻松匹配UTF-16编码的代理对。
请查看文档:

http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html

Unicode字符也可以通过直接使用其十六进制代码点值在正则表达式中表示,如构造 \x{...} 所描述的那样。例如,补充字符 U+2011F 可以被指定为 \x{2011F},而不是由代理对的两个连续的Unicode转义序列 \uD840\uDD1F。

然而,如果您无法切换到Java 7,您可以扩展Guava提供的有价值的UnicodeEscaper

这里是一个示例实现:

public class SimpleEscaper extends UnicodeEscaper
{
    @Override
    protected char[] escape(int codePoint)
    {
        if (0x1f000 >= codePoint && codePoint <= 0x1ffff)
        {
            return Integer.toHexString(codePoint).toCharArray();
        }

        return Character.toChars(codePoint);
    }
}

4

表情符号正则表达式

public static final String sEmojiRegex = "(?:[\\u2700-\\u27bf]|" +

        "(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
        "[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?" +

        "(?:\\u200d(?:[^\\ud800-\\udfff]|" +

        "(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
        "[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?)*|" +

        "[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|[\\ud83c\\udd70-\\ud83c\\udd71]|[\\ud83c\\udd7e-\\ud83c\\udd7f]|\\ud83c\\udd8e|[\\ud83c\\udd91-\\ud83c\\udd9a]|[\\ud83c\\udde6-\\ud83c\\uddff]|[\\ud83c\\ude01-\\ud83c\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|[\\ud83c\\ude32-\\ud83c\\ude3a]|[\\ud83c\\ude50-\\ud83c\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff]";

一些表情符号 (1627)

// count = 1627
public static final String sEmojiTest = "☺️☹️☠️✊✌️☝️✋✍️‍♀‍♀‍♀‍♀‍♀️‍♀️‍⚕‍⚕‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍✈‍✈‍‍‍⚖‍⚖‍♀‍♂‍♂‍♂‍♂‍♀‍♂‍♀‍♂‍♂‍♂‍♂‍♂‍♂‍♀‍♀‍❤️‍‍❤️‍‍❤️‍‍‍❤️‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍⛑☂️☘️⭐️✨⚡️☄☀️⛅️☁️⛈☃️⛄️❄️☔️☕️⚽️⚾️⛳️⛸⛷️‍♀️‍♀‍♂‍♀‍♂⛹️‍♀️⛹‍♀‍♂️‍♀️‍♀‍♀‍♀‍♂‍♀‍♀‍♀‍♀‍♂✈️⛵️⛴⚓️⛽️⛲️⛱⛰⛺️⛪️⛩⌚️⌨️☎️⏱⏲⏰⌛️⏳⚖️⚒⛏⚙️⛓⚔️⚰️⚱️⚗️✉️✂️✒️✏️❤️❣️☮️✝️☪️☸️✡️☯️☦️⛎♈️♉️♊️♋️♌️♍️♎️♏️♐️♑️♒️♓️⚛️☢️☣️️️✴️㊙️㊗️️️️❌⭕️⛔️♨️❗️❕❓❔‼️⁉️〽️⚠️⚜️♻️✅️❇️✳️❎Ⓜ️♿️️️ℹ️0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣#️⃣*️⃣▶️⏸⏯⏹⏺⏭⏮⏩⏪⏫⏬◀️➡️⬅️⬆️⬇️↗️↘️↙️↖️↕️↔️↪️↩️⤴️⤵️➕➖➗✖️™️©️®️〰️➰➿✔️☑️⚪️⚫️▪️▫️◾️◽️◼️◻️⬛️⬜️‍♠️♣️♥️♦️️️️‍⚽️⚾️⛳️⛸⛷️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️️‍♀️‍♂️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♂️‍♂️‍♂️‍♂️‍♂️‍♂️⛹️‍♀️⛹‍♀️⛹‍♀️⛹‍♀️⛹‍♀️⛹‍♀️⛹️⛹⛹⛹⛹⛹‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♂️‍♂️‍♂️‍♂️‍♂️‍♂️️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♂️‍♂️‍♂️‍♂️‍♂️‍♂️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♀️‍♂️";

测试表情符号的函数

public void checkMatchingEmojis() {

    final Pattern pattern = Pattern.compile(sEmojiRegex);
    final Matcher matcher = pattern.matcher(sEmojiTest);
    int foundEmojiCount = 0;
    while (matcher.find()) {
        System.out.println("Full match: " + matcher.group(0));
        foundEmojiCount++;
    }
    System.out.println("*******************************************");
    System.out.println("Input Emoji count = 1627");
    System.out.println("Captured Emoji count = " + foundEmojiCount);
    System.out.println("*******************************************");

}

这里是代码片段,已测试所有的Unicode 10表情符号。

感谢Kevin Scott提供了很好的示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接