使用java 8在文件中查找模式

考虑我有一个文件(只是一个摘录)

name: 'foobar' 

当我发现name的行时,我喜欢检索foobar

我目前的做法是

 Pattern m = Pattern.compile("name: '(.+)'"); try (Stream lines = Files.lines(ruleFile)) { Optional 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 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中缺少此function,但是,潜入即将发布的版本有助于了解临时解决方案的外观:

 Matcher m = Pattern.compile("name: '(.+)'").matcher(""); try(Stream 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 lines = Files.lines(ruleFile)) { lines.flatMap(line -> pattern.matcher(line).results().limit(1)) .forEach(mr -> System.out.println(mr.group(1))); } 

RESP。 用Java 8

 try(Stream 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))); } 

由于引入了局部变量,这并不简洁。 这可以通过前面的map操作来避免,但是当我们处于这一点时,只要我们每行只进行一次匹配,我们就不需要flatMap

 try(Stream lines = Files.lines(ruleFile)) { lines.map(pattern::matcher).filter(Matcher::find) .forEach(m -> System.out.println(m.group(1))); } 

由于每个Matcher只使用一次,以非干扰的方式,其可变性质在这里不会受到伤害,并且转换为不可变的MatchResult变得不必要。

但是,如果有必要,这些解决方案无法按比例缩放以处理每行多个匹配…

@khelwood的答案导致一遍又一遍地创建一个新的Matcher对象,如果扫描长文件,这可能是效率低下的根源。

以下解决方案仅创建一次匹配器,并为文件中的每一行重用它。

 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 ,违反了有关副作用的各种规则等。

当然,这不能用于并行流,但幸运的是从文件中读取本质上是顺序的。

黑客还是优化? 我想上/下投票将决定。