从字符串中删除特定Unicode范围的字符

17
我有一个程序,可以实时解析来自Twitter流API的推文。在存储它们之前,我将它们编码为utf8。某些字符以?、??或???的形式出现在字符串中,而不是它们各自的Unicode代码,并引起问题。经过进一步调查,我发现有问题的字符来自于“表情符号”块,U+1F600 - U+1F64F,以及“杂项符号和象形文字”块,U+1F300 - U+1F5FF。我尝试了删除,但未成功,因为匹配器最终替换了字符串中几乎每个字符,而不仅仅是我所需的Unicode范围。
String utf8tweet = "";
        try {
            byte[] utf8Bytes = status.getText().getBytes("UTF-8");

            utf8tweet = new String(utf8Bytes, "UTF-8");

        } 
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
Pattern unicodeOutliers = Pattern.compile("[\\u1f300-\\u1f64f]", Pattern.UNICODE_CASE | Pattern.CANON_EQ | Pattern.CASE_INSENSITIVE);
Matcher unicodeOutlierMatcher = unicodeOutliers.matcher(utf8tweet);
utf8tweet = unicodeOutlierMatcher.replaceAll(" ");

我该怎么做才能去除这些字符?


当你说它不起作用时,你看到了什么行为?你是否尝试使用单个字符而不是使用范围[\u1f300-\u1f64f],并查看它是否有效?我怀疑正则表达式范围语法会在处理Unicode字符时出现问题。 - Rajesh J Advani
1
如果在GUI组件或IDE控制台输出中显示Unicode编码的字符串时,您看到的是?而不是Unicode字符,请不要担心,这不是由于Unicode编码,而是由于选择了不支持Unicode代码点(仅有255个代码点)的显示字体,例如Latin-1字体。尝试使用任何支持Unicode的字体,如Arial Unicode MS。 - ecle
抱歉没有说得够清楚!我所指的“不起作用”是指匹配器没有找到该字符,或者至少replaceAll函数没有应用于它。谢谢,eee!那是一个好观点。然而,我注意到我的输出中有Unicode(即“u20A2”),而问题中的字符仍然是“??”。 - Saiato
5个回答

36
在正则表达式模式中添加否定运算符^。对于过滤可打印字符,您可以使用以下表达式[^\\x00-\\x7F],您应该会得到所需的结果。
import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UTF8 {
    public static void main(String[] args) {
        String utf8tweet = "";
        try {
            byte[] utf8Bytes = "#Hello twitter  How are you?".getBytes("UTF-8");

            utf8tweet = new String(utf8Bytes, "UTF-8");

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Pattern unicodeOutliers = Pattern.compile("[^\\x00-\\x7F]",
                Pattern.UNICODE_CASE | Pattern.CANON_EQ
                        | Pattern.CASE_INSENSITIVE);
        Matcher unicodeOutlierMatcher = unicodeOutliers.matcher(utf8tweet);

        System.out.println("Before: " + utf8tweet);
        utf8tweet = unicodeOutlierMatcher.replaceAll(" ");
        System.out.println("After: " + utf8tweet);
    }
}

结果呈现如下输出:

Before: #Hello twitter  How are you?
After: #Hello twitter   How are you?

编辑

进一步解释一下,你也可以使用\u形式来表示范围,如下所示 [^\\u0000-\\u007F],这将匹配所有不是前128个UNICODE字符的字符(与之前相同)。如果您想扩展范围以支持额外的字符,则可以使用UNICODE字符列表here

例如,如果您想包括带重音符号的元音(在西班牙语中使用),则应将范围扩展到\u00FF,因此您有[^\\u0000-\\u00FF][^\\x00-\\xFF]

Before: #Hello twitter  How are you? á é í ó ú
After: #Hello twitter   How are you? á é í ó ú

有问题的字符已经被移除了! :) (在这种情况下,?代表其中一个有问题的字符)但是所有字符都被移除了...包括# ! . `BEFORE:#MentionSomeoneYouDontWannaLose@OG_RiiSky!否则我会?。 AFTER:MentionSomeoneYouDontWannaLose@OG_RiiSky或者我会d。问题字符是否被移除是因为正则表达式认为它实际上是一个问号,还是它确实能够从该范围中提取? - Saiato
你说得对。我编辑了答案,更改了使用的正则表达式,它只会匹配可打印字符。 - juan.facorro
谢谢! 这样工作得好多了 :) 出于好奇,您是如何从 Unicode 字符范围获取该新模式的? 它似乎正在消除范围之外的某些字符 BEFORE: RT @JulianSerrano01: #ContraseñasQueTuve "notelavoyadecir" le puse esa contraseña a la unica PC de la casa en ese momento, se las decia ... AFTER: RT @JulianSerrano01: #ContraseñasQueTuve "notelavoyadecir" le puse esa contraseña a la unica PC de la casa en ese momento, se las decia ... - Saiato
我是从我回答不久前的另一个SO问题中得到的 :)(请参见评论末尾的链接)。一开始我没有想到它,但后来它似乎是一个合适的解决方案。所提出的正则表达式寻找那些不可打印的字符,也就是不在指定范围内的字符。http://stackoverflow.com/questions/11811301/regular-expression-for-allowed-characters/11811338#11811338 - juan.facorro
3
谢谢您的编辑!我已更改模式中的Unicode范围以指定我想要允许的所有字符。它现在完美地工作着 :) 对于任何感兴趣的人,我使用的模式是 [^\\u0000-\\uFFEF],它允许几乎所有特殊字符和表情符号之前的字符,这些特殊字符和表情符号可能会使我的程序出错。 - Saiato

24

3
在正确的Java代码中,您可以使用s.replaceAll("\\p{So}+", "")(声明为OTHER_SYMBOLS)来实现。 - Marcel
2
你如何确定"So"对应于杂项符号?我目前正在使用块的冗长形式:[\\p{InMiscellaneousSymbolsAndPictographs}|\\p{InEmoticons}]+ - bcoughlan
@bcoughlan 是的,那就是我最初使用长名称的原因,为了在javadoc中找到它。虽然肯定太长了,但至少是自我记录的。 - Joop Eggen
@bcoughlan 在 Java Pattern javadoc 中发现了这个链接:http://www.unicode.org/reports/tr18/。请查看分类。 - Joop Eggen
@bcoughlan 好的,“So”可以在Java文档中找到:https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#OTHER_SYMBOL - Joop Eggen

7

我尝试过这个。Unicode范围来自于表情符号的范围

    class EmojiEraser{

    private static final String EMOJI_RANGE_REGEX =
                "[\uD83C\uDF00-\uD83D\uDDFF]|[\uD83D\uDE00-\uD83D\uDE4F]|[\uD83D\uDE80-\uD83D\uDEFF]|[\u2600-\u26FF]|[\u2700-\u27BF]";
        private static final Pattern PATTERN = Pattern.compile(EMOJI_RANGE_REGEX);

        /**
         * Finds and removes emojies from @param input
         * 
         * @param input the input string potentially containing emojis (comes as unicode stringfied)
         * @return input string with emojis replaced
         */
        public String eraseEmojis(String input) {
            if (Strings.isNullOrEmpty(input)) {
                return input;
            }
            Matcher matcher = PATTERN.matcher(input);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, "");
            }
            matcher.appendTail(sb);
            return sb.toString();
        }
}

这个正则表达式不起作用,你有另一个解决方案吗?因为当我在在线上使用这个正则表达式和我的字符串时,它没有给出积极的报告。我的 Unicode 字符串是 \u263A\uD83D\uDE0A\uD83D\uDE22\uD83D\uDC4D。 - Kishan Donga

0
假设status.getText()返回一个java.lang.String...
byte[] utf8Bytes = status.getText().getBytes("UTF-8");
utf8tweet = new String(utf8Bytes, "UTF-8");

上述转码操作产生的结果与以下操作相同:

utf8tweet = status.getText();

Java字符串隐式地采用UTF-16编码。UTF-16和UTF-8共享相同的字符集(Unicode),因此从一种编码转换到另一种编码,再转回来可以保持原始数据。

Java正则表达式支持使用 代理对进行补充范围匹配。您可以按照这个问题的答案中所述进行匹配。

正如 eee在他的评论中所指出的那样,您很可能存在字体问题。一个字形能否被显示通常取决于用户系统上可用的字体、所选择的字体以及渲染技术支持的字体替换形式。


我知道字体可能无法呈现该字符,但问题是我通过socket.io将这些字符串发送到我的node.js服务器。当服务器上的node遇到该字符时,它会将其读取为“传输结束(未定义)”,并断开我的连接。因此,必须以某种方式删除这些字符 :) - Saiato
@Saiato - 听起来像是传输协议的问题。 - McDowell

0
如果您不想使用正则表达式,可以直接测试unicode块
private static final Set<Character.UnicodeBlock> BLACKLIST=Set.of(
    Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS,
    Character.UnicodeBlock.EMOTICONS);

public String sanitize(String verbatim) {
    int cps=verbatim.codePoints()
        .filter(cp -> !BLACKLIST.contains(Character.UnicodeBlock.of(cp)))
        .toArray();
    return new String(cps, 0, cps.length);
}

此外,如今Java中的表情符号处理库非常出色,有些还可以处理象形文字,比如sigpwned/emoji4j。使用该库,您可以编写以下代码:
public String sanitize(String verbatim) {
    return new GraphemeMatcher(verbatim).replaceAll(mr -> "");
}

免责声明:我是该库的作者,所以对其实用性和简洁性可能有偏见。 :)

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