我目前正在使用以下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);
}
}
}
}
预期输入可以是包含用户名的任何内容。我想提取一个被认为是合法的用户名,以'@'开头,后跟任意数量的数字或字符'a' - 'z','A' - 'Z',0-9和'_',开头与 '@'。
如果在'@'之后立即出现任何非法字符,我们会忽略,但我们希望提取其他合法用户名或非法字符之前或之后的用户名。
作为示例输入:
“!@@@ Mike,#Java @ Nancy_2,这个尺寸”
应该返回:
迈克Nancy_2
答案应该适用于Java。
答案 0 :(得分:5)
从你的解释:
预期输入可以是包含用户名的任何内容。我想提取与任何字符'a' - 'z','A' - 'Z',0-9和'_'合法的用户名,以'@'开头。如果在'@'之后立即出现任何非法字符,我们会忽略,但我们希望提取其他合法用户名或非法字符之前或之后的用户名
我们似乎正在搜索[\w]
的{{1}}(这是[a-zA-Z0-9_]
的简写),紧接着是@
。这在Regex中非常简单,主要的担心是消除回溯和几乎匹配的成本。
模式:
(?<=@)[\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
上循环索引。这是一个非常糟糕的主意 - 特别是因为您不知道List
Splitter.splitToList
返回什么类型。如果恰好是LinkedList
,那么访问by-index为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 :除非您:
,否则切勿使用by-index循环List
是RandomAccess
;和进一步的补充,如果你想成为Java 8-y,你可以使用以下代码将Matcher
包裹在Spliterator
中:
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());
}
的灵感