java - 从 Guava CharMatcher 切换到 Regex

标签 java regex guava

我目前正在使用以下 CharMatcher 算法来解析包含 1000 万条推文的文件中 Twitter 状态中的所有 @Mentions。它似乎正在消耗大量内存。运行 Netbeans 分析器,它似乎创建了很多 char[] 数组,我只能假设它们来 self 实现的 CharMatcher 解决方案。

任何人都可以推荐一个更高效的 CharMatcher/Strings 方法或正则表达式解决方案(我认为在对象创建方面会更有效)?速度不是我最关心的......

@Override
public boolean filter(Tweet msg) {

    List<String> statusList = Splitter.on(CharMatcher.BREAKING_WHITESPACE).trimResults().omitEmptyStrings().splitToList(msg.getStatusText());

    for (int i = 0; i < statusList.size(); i++) {
        if (statusList.get(i).contains("@")) {
            insertTwitterLegalUsernames(statusList.get(i), msg);
        }
    }

    if (msg.hasAtMentions()) {
        Statistics.increaseNumTweetsWithAtMentions();
    }

    statusList = null;
    return msg.hasAtMentions();
}

private void insertTwitterLegalUsernames(String token, Tweet msg) {
    token = token.substring(token.indexOf("@"), token.length());
    List<String> splitList = Splitter.on(CharMatcher.inRange('0', '9').or(CharMatcher.inRange('a', 'z')).or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.anyOf("_@")).negate()).splitToList(token);
    for (int j = 0; j < splitList.size(); j++) {
        if (splitList.get(j).length() > 1 && splitList.get(j).contains("@")) {
            String finalToken = splitList.get(j).substring(splitList.get(j).lastIndexOf("@") + 1, splitList.get(j).length());
            if (!finalToken.equalsIgnoreCase(msg.getUserScreenNameString())) {
                msg.addAtMentions(finalToken);
            }
        }
    }

}

预期的输入可以是任何包含用户名的内容。我想提取被认为以“@”开头的合法用户名,后跟任意数量的数字或字符“a”-“z”、“A”-“Z”、0-9 和“_”,开头带有“@”。

如果紧随“@”之后有任何非法字符,我们将忽略,但我们希望提取位于其他合法用户名或非法字符之前或之后的用户名。

作为示例输入:

"!@@@Mike,#Java@Nancy_2,this this on for size"

应该返回:

Mike

Nancy_2

答案对于 Java 中的使用应该有效。

最佳答案

根据您的解释:

The expected input could be anything with username's throughout it. I want to extract the username which is legal with any character 'a' - 'z', 'A' - 'Z', 0-9 and '_', beginning with an '@'. Should there be any illegal characters immediately following the '@', we would disregard, however we would expect to extract usernames that are either before or after either other legal usernames or illegal characters

看来我们正在寻找[\w] (这是 [a-zA-Z0-9_] 的简写),其前面紧接着是 @ 。这在正则表达式中非常简单,主要担心的是消除回溯和几乎匹配的成本。

模式:

(?<=@)[\w]++

会完全按照您的要求去做。

打破模式:

  • (?<=@)是一个肯定的后向断言,以检查@这场比赛之前
  • [\w]++所有格匹配名称本身,它必须至少包含一个字符。

首先,声明Pattern 全局。它是线程安全的,应该重用。

private static final Pattern TWITTER_NAME = Pattern.compile("(?<=@)[\\w]++")

然后您可以使用诸如此类的方法来提取(唯一)用户名:

public static Set<String> findNames(final String input) {
    final Matcher matcher = TWITTER_NAME.matcher(input);
    final Set<String> names = new HashSet<>();
    while (matcher.find()) {
        names.add(matcher.group());
    }
    return names;
}

请注意,您还可以重复使用 Matcher reset(String) ,但是 Matcher 不是线程安全 - 您可以考虑使用 ThreadLocal如有必要,匹配器实例可提高性能。如果不使用多线程,那么可以使用全局Matcher也是。

使用您的输入进行测试:

public static void main(final String[] args) throws Exception {
    System.out.println(findNames("!@@@Mike,#Java@Nancy_2,this this on for size"));
}

产量:

[Mike, Nancy_2]
<小时/>

作为旁注,您正在按索引循环所有 List s。这是一个非常糟糕的主意 - 特别是当您不知道 List 是什么类型时Splitter.splitToList返回。如果它恰好是LinkedList那么按索引访问是 O(n)所以虽然这个循环:

for(final String s : myList) {
    System.out.println(s);
}

显然是O(n) ,按索引进行相同的循环:

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

很容易是O(n^2) 。这是毫无理由的巨大性能损失。

TL;DR:切勿使用按索引循环,除非您:

  1. 知道您的ListRandomAccess ;和
  2. 出于某种原因确实需要索引。
<小时/>

进一步的补充,如果你想成为 Java 8-y,你可以使用下面的代码来包装 MatcherSpliterator :

public class MatcherSpliterator extends AbstractSpliterator<MatchResult> {

    private final Matcher m;

    public MatcherSpliterator(final Matcher m) {
        super(Long.MAX_VALUE, ORDERED | NONNULL | IMMUTABLE);
        this.m = m;
    }

    @Override
    public boolean tryAdvance(Consumer<? super MatchResult> action) {
        if (!m.find()) {
            return false;
        }
        action.accept(m.toMatchResult());
        return true;
    }
}

然后是一个简单的方法来返回 Stream 中的匹配结果:

public static Stream<MatchResult> extractMatches(final Pattern pattern, final String input) {
    return StreamSupport.stream(new MatcherSpliterator(pattern.matcher(input)), false);
}

现在你的方法变成了:

public static Set<String> findNames(final String input) {
    return extractMatches(TWITTER_NAME, input)
            .map(MatchResult::group)
            .collect(toSet());        
}

灵感来自 this SO answer

关于java - 从 Guava CharMatcher 切换到 Regex,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29284882/

相关文章:

java - 不在 App Server 中时伪造 JNDI

java - 我可以将 String 变量转换为 int 变量吗

Javascript:使用正则表达式反向引用引用数组元素

python - 正则表达式帮助匹配多个连续组

java - 如何将字节从 bytesource 复制到 bytesink

java - 读取 mp3 二进制数据以进行可视化

java - 如何在2D平面上投影3D?

c# - 正则表达式获取模式的最后一次出现

java - Guava 缓存 RemovalListener 未调用

java - 将 map 转换为排序列表