Java扫描程序使用模式(缓冲区边界问题)

Java扫描程序使用模式(缓冲区边界问题),java,regex,java.util.scanner,Java,Regex,Java.util.scanner,执行摘要:在Java的扫描器中使用\R(或其他正则表达式模式)是否有任何警告/已知问题(特别是关于内部缓冲区的边界条件) 详细信息:因为我想在潜在的多平台输入文件上进行多行模式匹配,所以我使用了\R模式,根据模式javadoc: 任何Unicode换行符序列,都等效于 \u000D\u000A |[\u000A\u000B\u000C\u000D\u0085\u2028\u2029] 无论如何,我在我的一个测试文件中注意到,应该解析十六进制转储块的循环被缩短了。经过一些调试,我注意到它结束的那一

执行摘要:在Java的
扫描器中使用
\R
(或其他正则表达式模式)是否有任何警告/已知问题(特别是关于内部缓冲区的边界条件)

详细信息:因为我想在潜在的多平台输入文件上进行多行模式匹配,所以我使用了
\R
模式,根据
模式
javadoc:

任何Unicode换行符序列,都等效于
\u000D\u000A |[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

无论如何,我在我的一个测试文件中注意到,应该解析十六进制转储块的循环被缩短了。经过一些调试,我注意到它结束的那一行是扫描仪内部缓冲区的末尾

下面是我为模拟这种情况而编写的测试程序:

public static void main(String[] args) throws IOException {
    testString(1);
    testString(1022);
}

private static void testString(int prefixLen) {
    String suffix = "b\r\nX";
    String buffer = new String(new char[prefixLen]).replace("\0", "a") + suffix;

    Scanner scanner = new Scanner(buffer);
    String pattern = "b\\R";
    System.out.printf("=================\nTest String (Len=%d): '%s'\n'%s' found with horizon=0 (w/o bound): %s\n", buffer.length(), convertLineEndings(
        buffer), pattern, convertLineEndings(scanner.findWithinHorizon(pattern, 0)));
    System.out.printf("'X' found with horizon=1: %b\n", scanner.findWithinHorizon("X", 1) != null);
    scanner.close();
}

private static String convertLineEndings(String string) {
    return string.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r");
}
。。。生成此输出(为格式/简洁而编辑):

=================
测试字符串(Len=5):'ab\r\nX'
找到地平线为0的“b\R”(未绑定):b\R\n
发现地平线为1的“X”:真
=================
测试字符串(Len=1026):'a。。。ab\r\nX'
找到地平线为0的“b\R”(未绑定):b\R
发现地平线为1的“X”:false
对我来说,这看起来像一只虫子!我认为扫描器应该以相同的方式将
后缀
与模式匹配,而不依赖于它们在输入文本中的显示位置(只要
前缀
与模式无关)。(我还发现了可能相关的OpenJDK bug,但这是针对常规oraclejdk8u111的)

但我可能错过了一些关于扫描器或特定
\R
模式使用的建议(或者OpenJDK和Oracle在这里对相关类有相同的(?)实现?)。。。这就是问题所在

两个建议:

我认为你应该用这种方法测试X:

System.out.printf("'X' found with horizon=1: %b\n", 
    scanner.findWithinHorizon("X", prefixLen) != null);
(因为除了0以外的任何其他参数都会将搜索限制在一定数量的字符内。这已经在方法的名称中。方法所看到的范围就是地平线。)

您的文件编码可能有问题。 扫描仪可能会选择错误的默认编码。试着这样做:

new Scanner(file, "utf-8");

我在Ideone上测试了这段代码,在最新版本的Java上不再返回“false”

但是,如果我被困在一个旧版本或仍然存在缺陷的版本上,并且我需要一个通用的解决方案,而不是这个示例的解决方法,那么我可能会尝试制作一个类似于
\R
的正则表达式,但这会强制在
\R
案例中额外查看字节。请注意,文档中所谓的“等效”模式是,因为它实际上需要是一个原子分组。所以你可能会得到这样的结果:


(?>\u000D\u000A |[\u000A\u000B\u000C\u000D\u0085\u2028\u2029](?=。|\Z))

是的,在我的业务逻辑中,扩大范围(对于“X”)是一个可用的选项,这也是我选择的解决方法,但情况可能并非总是如此。如果前面的代码“跳过”了新行,那么下一个查找应该能够假设是这样,并相应地采取行动。编码在这里不是一个问题(如果我只是在一个文件中遇到这个问题的话),但是从上面的示例代码中可以看到,它确实发生在普通的文本Java字符串中(使用标准ASCII的字符)。所以你在代码中看到的应该是你(扫描器)得到的!。。。我不想让自己听起来不那么挑剔,但这对我没有帮助(因为我已经考虑过扩大视野,但没有把它作为“真正的解决方案”,因为在解析逻辑中,这可能并不总是一个可行的选择)。我非常感谢您抽出时间发送答案,但我的问题的要点是,扫描仪不应该根据输入长度(或其内部缓冲区的终点/覆盖范围)采取不同的操作。它仍然可以帮助其他人。这取决于您……这不会是Java的正则表达式方法中的第一个bug:谢谢您的跟进!我选择这个作为“答案”,因为它确认了所看到的行为确实是一个bug,在以后的版本中是“修复”的。我想知道哪个版本有修复程序会很好,于是我跟随您的脚步,尝试了同样允许JDK版本选择的方法。在那里,它在JDK 9.0.1中失败,但在JDK 10.0.1中通过(返回“true”)