使用 \R 模式使用 Java 扫描仪(缓冲区边界问题)

Posted

技术标签:

【中文标题】使用 \\R 模式使用 Java 扫描仪(缓冲区边界问题)【英文标题】:Java scanner usage with \R pattern (issue with buffer boundary)使用 \R 模式使用 Java 扫描仪(缓冲区边界问题) 【发布时间】:2018-08-10 18:23:26 【问题描述】:

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

详情:因为我想对潜在的多平台输入文件进行一些多行模式匹配,所以我使用\R 的模式,根据Pattern javadoc 是:

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

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

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

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

...产生此输出(为格式化/简洁而编辑):

=================
Test String (Len=5): 'ab\r\nX'
'b\R' found with horizon=0 (w/o bound): b\r\n
'X' found with horizon=1: true
=================
Test String (Len=1026): 'a ... ab\r\nX'
'b\R' found with horizon=0 (w/o bound): b\r
'X' found with horizon=1: false

对我来说,这看起来像一个错误!我认为扫描仪应该以相同的方式将suffix 与模式匹配,而与它们在输入文本中的显示位置无关(只要prefix 不涉及模式)。 (我还发现了可能相关的Open JDK 错误8176407 和8072582,但这是针对常规Oracle JDK 8u111 的)。

但我可能错过了一些关于扫描器或特定 \R 模式使用的建议(或者 Open JDK 和 Oracle 在这里的相关类有相同的(??)实现?)......因此问题!

【问题讨论】:

我不想听起来不欣赏,但这对我没有帮助(因为我已经考虑过扩大视野,但没有选择作为“真正的解决方案”作为这在解析逻辑中可能并不总是一个可行的选择)。我非常感谢您抽出时间来发送答案,但我的问题的要点是 Scanner 不应该根据输入长度(或其内部缓冲区结束/覆盖的内容)采取不同的行动。它仍然可以帮助其他人。这取决于你... 这不会是 Java 正则表达式方法中的第一个错误:***.com/a/49264884/3600709 【参考方案1】:

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

https://www.ideone.com/4wwYSj

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

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

【讨论】:

感谢您的跟进!我选择它作为“答案”,因为它确认所看到的行为确实是一个错误,在以后的版本中已“修复”。想知道哪个版本有修复会很好,我跟随你的脚步,尝试了the code on JDoodle,它也允许选择 JDK 版本。在那里,它使用 JDK 9.0.1 失败,但使用 JDK 10.0.1 通过(返回“true”)【参考方案2】:

两个建议:

我认为你应该这样测试 X:

System.out.printf("'X' found with horizon=1: %b\n", 
    scanner.findWithinHorizon("X", prefixLen) != null);

(因为除 0 之外的任何参数都将搜索限制为一定数量的字符。这已经在方法的名称中。地平线是方法所看到的。)

您的文件编码可能有问题。 您的扫描仪可能会选择错误的默认编码。尝试类似的方法:

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

【讨论】:

是的,扩大视野(对于“X”)是我的业务逻辑中的一个可用选项,这也是我选择的解决方法,但可能并非总是如此。如果前面的代码“跳过”了新行,那么下一个 find 应该能够假设是这种情况,并采取相应的行动。编码在这里不是问题(如果我只用文件遇到这个问题),但正如您从上面的示例代码中看到的那样,它确实发生在普通文字 Java 字符串(来自标准 ASCII 的字符)中。所以你在代码中看到的应该是你(扫描仪)得到的!...

以上是关于使用 \R 模式使用 Java 扫描仪(缓冲区边界问题)的主要内容,如果未能解决你的问题,请参考以下文章

模式匹配算法:扫描+特征比较

Java:模式在扫描仪中的行为不同

Java使用扫描仪输入键按下

扫描仪与 BufferedReader

python 第二天

Java 扫描程序问题