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

时间:2016-01-14 13:40:37

标签: java regex java-8

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

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);
    }
}

看起来不太好。过度使用模式和匹配器似乎是错误的。

有更简单/更好的方法吗?特别是如果我有多个键我喜欢这样搜索?

3 个答案:

答案 0 :(得分:23)

我希望更像这样的东西,以避免两次匹配模式:

Maximum allowable value: maxResults=40.

也就是说,对于每个字符串的匹配器,获取匹配的第一个字符串,然后打印出第一个字符串。

答案 1 :(得分:7)

这就是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: '(.*?)'这样不情愿的量词时,情况会有所不同,这些量词会消耗 next '而不是 last ,或者禁止跳过明确',与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)));
}

RESP。使用Java 8

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)));
}

由于引入了局部变量而不是那么简洁。这可以通过前面的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变得不必要。

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

答案 2 :(得分:0)

@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,违反了有关边的各种规则 - 效果等。

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

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