Scanner.findAll()和Matcher.results()对于相同的输入文本和模式有不同的工作方式。

问题描述 投票:0回答:1

在使用regex分割属性字符串时,我看到了这个有趣的事情。我无法找到根本原因。

我有一个字符串,其中包含像属性键=值对这样的文本。= 的立场。它首先考虑的是 = 作为分割点。Value中也可以包含=。

我试着在Java中使用两种不同的方法来做。

  1. 使用 Scanner.findAll() 办法

    这没有达到预期的效果。它应该根据模式提取和打印所有键。但我发现它的行为很奇怪。我有一个键值对,如下所示

    SectionError.ErrorMessage=errorlevel=Warning {HelpMessage:This is very important message This is very important .....}

需要提取的密钥是 SectionError.ErrorMessage= 但它也考虑到 错误级别= 作为键。

有趣的是,如果我从传递的String属性中删除其中一个字符,它就会表现得很好,只提取了 SectionError.ErrorMessage= 键。

  1. 使用 Matcher.results() 办法

    这样就可以了。无论我们在属性字符串中放入什么都没有问题。

我尝试的示例代码。

import java.util.Scanner;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.MULTILINE;

public class MessageSplitTest {

    static final Pattern pattern = Pattern.compile("^[a-zA-Z0-9._]+=", MULTILINE);

    public static void main(String[] args) {
        final String properties =
                "SectionOne.KeyOne=first value\n" + // removing one char from here would make the scanner method print expected keys
                        "SectionOne.KeyTwo=second value\n" +
                        "SectionTwo.UUIDOne=379d827d-cf54-4a41-a3f7-1ca71568a0fa\n" +
                        "SectionTwo.UUIDTwo=384eef1f-b579-4913-a40c-2ba22c96edf0\n" +
                        "SectionTwo.UUIDThree=c10f1bb7-d984-422f-81ef-254023e32e5c\n" +
                        "SectionTwo.KeyFive=hello-world-sample\n" +
                        "SectionThree.KeyOne=first value\n" +
                        "SectionThree.KeyTwo=second value additional text just to increase the length of the text in this value still not enough adding more strings here n there\n" +
                        "SectionError.ErrorMessage=errorlevel=Warning {HelpMessage:This is very important message This is very important message This is very important messageThis is very important message This is very important message This is very important message This is very important message This is very important message This is very important message This is very important message This is very important messageThis is very important message This is very important message This is very important message This is very important message This is very important message}\n" +
                        "SectionFour.KeyOne=sixth value\n" +
                        "SectionLast.KeyOne=Country";

        printKeyValuesFromPropertiesUsingScanner(properties);
        System.out.println();
        printKeyValuesFromPropertiesUsingMatcher(properties);
    }

    private static void printKeyValuesFromPropertiesUsingScanner(String properties) {
        System.out.println("===Using Scanner===");
        try (Scanner scanner = new Scanner(properties)) {
            scanner
                    .findAll(pattern)
                    .map(MatchResult::group)
                    .forEach(System.out::println);
        }
    }

    private static void printKeyValuesFromPropertiesUsingMatcher(String properties) {
        System.out.println("===Using Matcher===");
        pattern.matcher(properties).results()
                .map(MatchResult::group)
                .forEach(System.out::println);

    }
}

打印输出。

===Using Scanner===
SectionOne.KeyOne=
SectionOne.KeyTwo=
SectionTwo.UUIDOne=
SectionTwo.UUIDTwo=
SectionTwo.UUIDThree=
SectionTwo.KeyFive=
SectionThree.KeyOne=
SectionThree.KeyTwo=
SectionError.ErrorMessage=
errorlevel=
SectionFour.KeyOne=
SectionLast.KeyOne=

===Using Matcher===
SectionOne.KeyOne=
SectionOne.KeyTwo=
SectionTwo.UUIDOne=
SectionTwo.UUIDTwo=
SectionTwo.UUIDThree=
SectionTwo.KeyFive=
SectionThree.KeyOne=
SectionThree.KeyTwo=
SectionError.ErrorMessage=
SectionFour.KeyOne=
SectionLast.KeyOne=

什么原因会导致这种情况?做扫描仪的 findAll 不同于 匹配器?

如果需要更多信息,请告诉我。

java regex pattern-matching java.util.scanner java-9
1个回答
2
投票

Scanner的文档中经常提到 "缓冲区 "这个词。这表明......的文档中经常提到 "缓冲区 "这个词。Scanner 不知道它所读取的整个字符串,每次只在缓冲区中保留一小部分。这是有道理的,因为 Scanner的设计也是为了从流中读取,从流中读取所有的东西可能需要很长的时间(,或者永远!),并且占用大量的内存。

在源码中的 Scanner,确实有一个 CharBuffer:

// Internal buffer used to hold input
private CharBuffer buf;

由于你的字符串长度和内容,扫描器决定将所有内容... ...

SectionError.ErrorMessage=errorlevel=Warning {HelpMessage:This is very...
                          ^
                    somewhere here
(It could be anywhere in the word "errorlevel")

...到缓冲区。然后,在这一半的字符串被读取后, 另一半的字符串就像这样开始。

errorlevel=Warning {HelpMessage:This is very...

errorLevel= 现在是字符串的开始,导致模式匹配。

相关BUG?

Matcher 不使用缓冲区。它在字段中存储了它所匹配的整个字符串。

/**
 * The original string being matched.
 */
CharSequence text;

所以这种行为并没有被观察到 Matcher.


2
投票

扫地机回答 对了,这是一个问题。Scanner的缓冲区不包含整个字符串。我们可以简化这个例子来具体触发这个问题。

static final Pattern pattern = Pattern.compile("^ABC.", Pattern.MULTILINE);
public static void main(String[] args) {
    String testString = "\nABC1\nXYZ ABC2\nABC3ABC4\nABC4";
    String properties = "X".repeat(1024 - testString.indexOf("ABC4")) + testString;

    String s1 = usingScanner(properties);
    System.out.println("Using Scanner: "+s1);
    String m = usingMatcher(properties);
    System.out.println("Using Matcher: "+m);

    if(!s1.equals(m)) System.out.println("mismatch");
    if(s1.equals(usingScannerNoStream(properties)))
        System.out.println("Not a stream issue");
}
private static String usingScanner(String source) {
    return new Scanner(source)
        .findAll(pattern)
        .map(MatchResult::group)
        .collect(Collectors.joining(" + "));
}
private static String usingScannerNoStream(String source) {
    Scanner s = new Scanner(source);
    StringJoiner sj = new StringJoiner(" + ");
    for(;;) {
        String match = s.findWithinHorizon(pattern, 0);
        if(match == null) return sj.toString();
        sj.add(match);
    }
}
private static String usingMatcher(String source) {
    return pattern.matcher(source).results()
        .map(MatchResult::group)
        .collect(Collectors.joining(" + "));
}

打印的是:

Using Scanner: ABC1 + ABC3 + ABC4 + ABC4
Using Matcher: ABC1 + ABC3 + ABC4
mismatch
Not a stream issue

这个例子在前缀中加入了尽可能多的... ... X 字符,以使假阳性匹配的开始与缓冲区的大小对齐。缓冲区中的 Scanner的初始缓冲区大小为 1024虽然在需要的时候可能会变大。

由于 findAll 忽略扫描仪的定界符,就像 findWithinHorizon,这段代码也表明,用 findWithinHorizon 手动表现出同样的行为,换句话说,这不是使用的Stream API的问题。

因为 Scanner 会在需要的时候扩大缓冲区,我们可以通过使用匹配操作来解决这个问题,在执行预期的匹配操作之前,强制将整个内容读入缓冲区,比如说

private static String usingScanner(String source) {
    Scanner s = new Scanner(source);
    s.useDelimiter("(?s).*").hasNext();
    return s
        .findAll(pattern)
        .map(MatchResult::group)
        .collect(Collectors.joining(" + "));
}

这个特定的 hasNext() 与消耗整个字符串的定界符一起使用,将强制对字符串进行完整的缓冲,而不会推进位置。随后的 findAll() 操作会同时忽略定界符和 hasNext() 检查,但由于缓冲区被完全填满,所以不会再受到这个问题的影响。

当然,这就破坏了 Scanner 当解析一个实际的流时。

© www.soinside.com 2019 - 2024. All rights reserved.