考虑我有一个文件(只是一个摘录)
name: 'foobar'
当我发现foobar
线时,我喜欢检索name
。
我目前的做法是
Pattern m = Pattern.compile("name: '(.+)'");
try (Stream<String> lines = Files.lines(ruleFile)) {
Optional<String> message = lines.filter(m.asPredicate()).findFirst();
if (message.isPresent()) {
Matcher matcher = m.matcher(message.get());
matcher.find();
String group = matcher.group(1);
System.out.println(group);
}
}
这看起来不太好。过度使用模式和匹配器似乎是错误的。
有更简单/更好的方法吗?特别是如果我有多个键我喜欢这样搜索?
我希望更像这样的东西,以避免两次匹配模式:
Pattern p = Pattern.compile("name: '([^']*)'");
lines.map(p::matcher)
.filter(Matcher::matches)
.findFirst()
.ifPresent(matcher -> System.out.println(matcher.group(1)));
也就是说,对于每个字符串的匹配器,获取匹配的第一个匹配,为此打印出第一个组。
这就是Java 9解决方案最有可能的样子:
Matcher m = Pattern.compile("name: '(.+)'").matcher("");
try(Stream<String> lines = Files.lines(ruleFile)) {
lines.flatMap(line -> m.reset(line).results().limit(1))
.forEach(mr -> System.out.println(mr.group(1)));
}
它使用Matcher.results()
方法返回所有匹配的流。通过flatMap
将一串线与一串匹配组合在一起,可以让我们处理文件的所有匹配。由于您的原始代码只处理一行的第一个匹配,我只是在每行的匹配中添加了一个limit(1)
以获得相同的行为。
遗憾的是,Java 8中缺少此功能,但是,潜入即将发布的版本有助于了解临时解决方案的外观:
Matcher m = Pattern.compile("name: '(.+)'").matcher("");
try(Stream<String> lines = Files.lines(ruleFile)) {
lines.flatMap(line -> m.reset(line).find()? Stream.of(m.toMatchResult()): null)
.forEach(mr -> System.out.println(mr.group(1)));
}
为了简化子流创建,该解决方案利用仅预期第一个匹配并且首先创建单个元素流。
但请注意,对于问题的模式'name: '(.+)'
,我们是否限制匹配的数量并不重要,因为.+
将贪婪地匹配所有字符直到该行的最后一个后续'
,因此另一场比赛是不可能的。当使用像name: '(.*?)'
这样不情愿的量词时,情况会有所不同,'
消耗到下一个'
而不是最后一个或禁止明确跳过name: '([^']*)'
,就像Matcher
一样。
上面的解决方案使用共享的Pattern
,它适用于单线程使用(并且这不太可能从并行处理中受益)。但是如果你想要在线程安全方面,你可能只共享一个Matcher
并创建一个m.reset(line)
而不是调用Pattern pattern = Pattern.compile("name: '(.*)'");
try(Stream<String> lines = Files.lines(ruleFile)) {
lines.flatMap(line -> pattern.matcher(line).results().limit(1))
.forEach(mr -> System.out.println(mr.group(1)));
}
:
try(Stream<String> lines = Files.lines(ruleFile)) {
lines.flatMap(line -> {Matcher m=pattern.matcher(line);
return m.find()? Stream.of(m.toMatchResult()): null;})
.forEach(mr -> System.out.println(mr.group(1)));
}
RESP。用Java 8
map
由于引入了局部变量,这并不简洁。这可以通过前面的flatMap
操作来避免,但是当我们处于这一点时,只要我们每行只进行一次匹配,我们就不需要try(Stream<String> lines = Files.lines(ruleFile)) {
lines.map(pattern::matcher).filter(Matcher::find)
.forEach(m -> System.out.println(m.group(1)));
}
了:
Matcher
由于每个MatchResult
只使用一次,以非干扰的方式,其可变性质在这里不会受到伤害,并且转换为不可变的Matcher
变得不必要。
但是,如果有必要,这些解决方案无法按比例缩放以处理每行多个匹配...
@khelwood的答案导致一遍又一遍地创建一个新的Pattern p = Pattern.compile("name: '([^']*)'");
Matcher matcher = p.matcher(""); // Create a matcher for the pattern
Files.lines(ruleFile)
.map(matcher::reset) // Reuse the matcher object
.filter(Matcher::matches)
.findFirst()
.ifPresent(m -> System.out.println(m.group(1)));
对象,如果扫描长文件,这可能是效率低下的根源。
以下解决方案仅创建一次匹配器,并为文件中的每一行重用它。
.map(matcher::reset)
警告 - 未来的可疑黑客
matcher.reset(line)
管道阶段是魔术/黑客发生的地方。它有效地调用matcher
,它重置.map(...)
以执行刚从文件读入的行上的下一个匹配,并返回自身,以允许链接调用。 Matcher
流操作符将此视为从线到matcher
对象的映射,但实际上,我们每次都映射到相同的对象qazxswpoi,违反了关于副作用的各种规则等。
当然,这不能用于并行流,但幸运的是从文件中读取本质上是顺序的。
黑客还是优化?我想上/下投票决定。