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

2022-09-03 17:29:07

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

name: 'foobar'

我喜欢在发现带有 .foobarname

我目前的做法是

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

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

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


答案 1

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

Pattern p = Pattern.compile("name: '([^']*)'");
lines.map(p::matcher)
     .filter(Matcher::matches)
     .findFirst()
     .ifPresent(matcher -> System.out.println(matcher.group(1)));

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


答案 2

这就是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() 返回所有匹配项的流。将行流与匹配流通过组合在一起,使我们能够处理文件的所有匹配项。由于您的原始代码只处理一行的第一个匹配项,因此我只需将 a 添加到每行的匹配项中即可获得相同的行为。flatMaplimit(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: '([^']*)'


上面的解决方案使用共享,它适用于单线程用法(这不太可能从并行处理中受益)。但是,如果你想在线程安全方面,你可以只共享一个并创建一个而不是调用:MatcherPatternMatcherm.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)));
}

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

由于引入了局部变量,这并不是那么简洁。这可以通过前面的操作来避免,但是当我们在这一点上时,只要我们每行只进行一场比赛,我们就不需要那么:mapflatMap

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

由于每个都以非干扰方式仅使用一次,因此其可变性在这里不会受到伤害,并且不需要转换为不可变性。MatcherMatchResult

但是,如果有必要,这些解决方案无法扩展到每行处理多个匹配项...