【问题标题】:Java Scanner newline parsing with regex (Bug?)Java Scanner 使用正则表达式解析换行符(错误?)
【发布时间】:2011-02-21 15:50:12
【问题描述】:

我正在用 Java 手动开发语法分析器,我想使用正则表达式来解析各种标记类型。问题是,如果输入不符合语法,我还希望能够准确地报告当前行号。

长话短说,当我尝试将换行符与 Scanner 类实际匹配时遇到了问题。具体来说,当我尝试使用 Scanner 类将换行符与模式匹配时,它会失败。几乎总是。但是,当我使用 Matcher 和相同的源字符串执行相同的匹配时,它也会完全按照您的预期检索换行符。有没有我似乎无法发现的原因,或者我怀疑这是一个错误?

仅供参考:我无法在 Sun 数据库中找到描述此问题的错误,因此如果是错误,则尚未报告。

示例代码:

Pattern newLinePattern = Pattern.compile("(\\r\\n?|\\n)", Pattern.MULTILINE);
String sourceString = "\r\n\n\r\r\n\n";
Scanner scan = new Scanner(sourceString);
scan.useDelimiter("");
int count = 0;
while (scan.hasNext(newLinePattern)) {
    scan.next(newLinePattern);
    count++;
}
System.out.println("found "+count+" newlines"); // finds 7 newlines
Matcher match = newLinePattern.matcher(sourceString);
count = 0;
while (match.find()) {
    count++;
}
System.out.println("found "+count+" newlines"); // finds 5 newlines

【问题讨论】:

    标签: java regex newline java.util.scanner


    【解决方案1】:

    您的 useDelimiter()next() 组合有问题。 useDelimiter("") 将在 next() 上返回长度为 1 的子字符串,因为空字符串实际上位于每两个字符之间。

    也就是说,因为"\r\n".equals("\r" + "" + "\n")所以"\r\n"实际上是两个标记,"\r""\n",由""分隔。

    要获得Matcher-行为,您需要findWithinHorizon,它会忽略分隔符。

        Pattern newLinePattern = Pattern.compile("(\\r\\n?|\\n)", Pattern.MULTILINE);
        String sourceString = "\r\n\n\r\r\n\n";
        Scanner scan = new Scanner(sourceString);
        int count = 0;
        while (scan.findWithinHorizon(newLinePattern, 0) != null) {
            count++;
        }
        System.out.println("found "+count+" newlines"); // finds 5 newlines
    

    API 链接

    • findWithinHorizon(Pattern pattern, int horizon)

      尝试查找指定模式的下一个匹配项 [...] 忽略分隔符 [...] 如果未检测到此类模式,则返回 null [...] 如果 horizon 为 0,然后 [...] 这个方法继续搜索输入,寻找指定的模式,没有限制。

    相关问题

    【讨论】:

      【解决方案2】:

      实际上,这就是两者的预期行为。扫描器主要关心使用分隔符将事物拆分为标记。所以它(懒惰地)获取你的 sourceString 并将其视为以下标记集:\r\n\n\r\r\n\n。然后,当您调用 hasNext 时,它会检查下一个标记是否与您的模式匹配(感谢\r\n? 上的?,它们都可以轻松完成)。因此,while 循环会遍历 7 个标记中的每一个。

      另一方面,匹配器会贪婪地匹配正则表达式 - 因此它会按照您的预期将 \r\ns 捆绑在一起。

      强调 Scanner 行为的一种方法是将您的正则表达式更改为 (\\r\\n|\\n)。这导致计数为 0。这是因为扫描器将第一个令牌读取为 \rnot \r\n),然后注意到它与您的模式不匹配,因此在以下情况下返回 false你打电话给hasNext

      (短版:扫描器在使用你的标记模式之前使用你的分隔符标记,匹配器不做任何形式的标记)

      【讨论】:

      • 所以要正确使用 Scanner,函数 useDelimiter 应该与换行模式一起使用,然后每次调用 next 都会给出一个对应于 next used 的重载版本的行。
      • 刚刚通过 Scanner.java 进行挖掘以找出答案。现在是后续问题:是否有一个类允许我仅基于正则表达式解析输入,而不是分隔标记?
      • @SEK 好吧,您可以像在示例中那样使用Matcher。或者你可以使用你的 newLinePattern 作为 Loki 所说的分隔符:)。 (但请注意,这意味着您无法区分 \r\n\r\n 分隔线,据我所知 - 扫描仪只会吞下分隔符;如果您迫切需要它们,您可以使用像(?<=\r)(?!\n)|(?<=\n) 这样可怕的东西作为分隔符,但我想我会在使用它之前研究更好的方法...... ;))
      • @Loki:感谢两位的快速回复。还要检查@polygenelubricants 的解决方案,他击中了头部。
      【解决方案3】:

      可能值得一提的是,您的示例模棱两可。可能是:

      \r
      \n
      \n
      \r
      \r
      \n
      \n
      

      (七行)

      或:

      \r\n
      \n
      \r
      \r\n
      \n
      

      (五行)

      ?您使用的量词是一个贪婪的量词,这可能会使五个正确答案,但是因为 Scanner 迭代令牌(在您的情况下是单个字符,由于您选择的分隔模式),它会不情愿地匹配,一次一个字符,得出错误的答案是 7。

      【讨论】:

        【解决方案4】:

        当您使用带有"" 分隔符的Scanner 时,它将生成每个字符长的标记。这是在应用新行正则表达式之前。然后它将这些字符中的每一个与新行正则表达式进行匹配;每一个都匹配,所以它产生 7 个令牌。但是,由于它将字符串拆分为 1 个字符的标记,它不会将相邻的 \r\n 字符组合成一个标记。

        【讨论】:

          猜你喜欢
          • 2020-09-11
          • 1970-01-01
          • 1970-01-01
          • 2010-11-22
          • 2016-03-31
          • 1970-01-01
          • 1970-01-01
          • 2021-07-18
          • 1970-01-01
          相关资源
          最近更新 更多