从Guava CharMatcher切换到Regex

Bro*_*oks 0 java regex guava

我目前正在使用以下CharMatcher算法在一个1000万条推文的文件中解析twitter状态中的所有@Mentions.它似乎占用了大量的记忆.运行Netbeans分析器,它似乎创建了很多char []数组,我只能假设它来自我实现的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);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

预期的输入可以是整个用户名的任何输入.我想提取一个被认为是合法的用户名,以'@'开头,后跟任意数量的数字或字符'a' - 'z','A' - 'Z',0-9和'_',开头与 '@'.

如果在'@'之后立即出现任何非法字符,我们会忽略,但是我们希望提取在其他合法用户名或非法字符之前或之后的用户名.

作为示例输入:

"!@@@ Mike,#Java @ Nancy_2,这个尺寸"

应该返回:

麦克风

Nancy_2

答案应该适用于Java.

Bor*_*der 5

从你的解释:

预期的输入可以是整个用户名的任何输入.我想提取与任何字符'a' - 'z','A' - 'Z',0-9和'_'合法的用户名,以'@'开头.如果在'@'之后立即出现任何非法字符,我们会忽略,但是我们希望提取在其他合法用户名或非法字符之前或之后的用户名

似乎我们正在搜索[\w](这是简写[a-zA-Z0-9_]),紧接着是一个@.这在Regex中非常简单,主要的担心是消除回溯和几乎匹配的成本.

模式:

(?<=@)[\w]++
Run Code Online (Sandbox Code Playgroud)

会完全按照你的要求行事.

打破模式:

  • (?<=@)是一个积极的后视断言,检查一个@先于这场比赛
  • [\w]++ 占有欲地匹配名称本身,它必须包含至少一个字符.

首先,宣布Pattern 全球.它是线程安全的,应该重用.

private static final Pattern TWITTER_NAME = Pattern.compile("(?<=@)[\\w]++")
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用此方法提取(唯一)用户名:

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以重用Matcherreset(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"));
}
Run Code Online (Sandbox Code Playgroud)

产量:

[Mike, Nancy_2]
Run Code Online (Sandbox Code Playgroud)

作为旁注,您将循环索引所有Lists.这是一个非常糟糕的主意 - 特别是因为您不知道什么类型的List Splitter.splitToList退货.如果它恰好是一个LinkedList然后访问by-index就是O(n)这样循环:

for(final String s : myList) {
    System.out.println(s);
}
Run Code Online (Sandbox Code Playgroud)

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

for(int i = 0; i < myList.size(); ++i) {
    System.out.println(myList.get(i));
}
Run Code Online (Sandbox Code Playgroud)

很容易O(n^2).绝对没有理由,这是一个巨大的性能损失.

TL; DR:永远不要使用by-index循环,除非你:

  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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在你的方法变成了:

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

这个SO答案的启示