【问题标题】:java.util.Scanner's useDelimiter(" ") or useDelimiter(Pattern.compile("\\s")) work different than standard behaviourjava.util.Scanner 的 useDelimiter(" ") 或 useDelimiter(Pattern.compile("\\s")) 与标准行为不同
【发布时间】:2017-05-11 12:08:02
【问题描述】:

给定下面的代码,它会输出:

Feed a chunk of data here:           
I have found:   0 words; 0 ints; 0 booleans;

如果我输入 10 个空格并留下两个 useDelimiter 方法调用的注释,并输出:

Feed a chunk of data here:           
I have found:   9 words; 0 ints; 0 booleans;
sssssssss

如果我键入了完全相同的 10 个空格,但确实使用了两个 useDelimiter 调用之一。 为什么会这样?不应该一样吗?这是代码,谢谢:

package com.riccardofinazzi.regex;

import java.io.Console;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.ArrayList;

class ScanNext {
    public static void main(String[] args) {

        /* match counters */
        int hits_s = 0, hits_i = 0, hits_b = 0;

        /* current token value */
        String  s;
        Integer i;
        Boolean b;

        ArrayList<Object> al = new ArrayList<Object>();

        Scanner s1 = new Scanner(System.console().readLine("Feed a chunk of data here: "));

        /* not needed as this is def behaviour, I put it here to not forget the method */

        //s1.useDelimiter(Pattern.compile("\\s"));
        //s1.useDelimiter(" ");

        while(s1.hasNext()) {
            if (        s1.hasNextInt()) {
                        al.add(s1.nextInt());       hits_i++;

            } else if ( s1.hasNextBoolean()) {
                        al.add(s1.nextBoolean());   hits_b++;

            } else {    al.add(s1.next());          hits_s++;
            }
        }

        System.out.println("I have found:\t"+hits_s+" words; "+hits_i+" ints; "+hits_b+" booleans;");

        for (Object in : al) {
            if (in instanceof String)
                System.out.print("s");
            if (in instanceof Integer)
                System.out.print("i");
            if (in instanceof Boolean)
                System.out.print("b");
        }
        System.out.print("\n");
    }
}

【问题讨论】:

    标签: java java.util.scanner delimiter


    【解决方案1】:

    假设X 是分隔符。

    如果我们扫描 "aXbXc" 这样的文本,很明显有 3 个标记:"a" "b""c"

    如果我们扫描像 "aXXc" 这样的文本,仍然有 3 个标记,但这次是:"a" """c"。这是因为我们将分隔符设置为一次仅匹配一个 X,因此它不会将另一个 X 视为已匹配分隔符的延续,而是作为单独的分隔符。
    (这在分隔符为 , 并且我们扫描像 1,2,,,3 这样的数据的情况下非常有用,因为它应该表示元素:1 2 noData noData 3 ).
    如果您希望分隔符表示一个或多个X,则需要使用X+,因为+ 是表示“一次或多次”的量词。这样aXXc 将仅代表"a""c" 元素,因为整个XX 将被视为一个分隔符。

    另一个有趣的案例是aXbX。如您所见,这里没有c文本以分隔符结尾。在这种情况下,Scanner 不会假设在最后一个分隔符之后有空元素,因此它只会将 "a""b" 视为标记,而不是 "a", "b", ""

    同样适用于XbXc,其中文本以分隔符开头。 Scanner 不会假设它之前有一些空元素。


    现在让我们回到你的案例。

    如果您打印扫描仪的默认分隔符(使用类似System.out.println(s1.delimiter()); 的代码),您将看到它是\p{javaWhitespace}+。所以默认分隔符是一个或多个空格。但稍后您将其更改为 单个 空格或一系列空格。这意味着对于字符串

    "          "
    
    • 如果分隔符为\p{javaWhitespace}+,则整个表达式匹配为一个分隔符,因此分隔符之前、之后和之间没有元素,因此有0个标记(非分隔符元素)
    • 但如果我们使用 " ""\\s" 作为分隔符,那么 Scanner 将找到 10 个分隔符(每个空格是其中的一个)。由于有 10 个分隔符,这意味着它们之间有 9 个元素 (即使是空字符串也算在内)。文本也以分隔符开头和结尾,这意味着在第一个分隔符之前或最后一个分隔符之后没有标记。

    【讨论】:

    • 它的行为似乎不像你描述的那样。对于useDelimiter(" "),以及以多个空格开头和结尾的输入,它将在开头和结尾处返回空字符串。
    • @KlitosKyriacou 我不确定你的意思。如果我们尝试扫描"___a___" 并设置分隔符_(我用_ 替换了空格,否则评论会将它们显示为单个空格)就像在ideone.com/xOSdYg 中一样,您将看到我们正在获取令牌""@987654364 @"a"""""。空字符串来自标有|: 的位置,因此它们来自分隔符_|_|_a_|_|_ 之间没有空字符串|___a___|,它们位于开始或尾随分隔符之后。
    • 您对我的评论的回复似乎与您在回答中所写的内容直接矛盾:Other interesting case is aXbX. As you see there is no c here, text ends with delimiter. In such case Scanner doesn't assume that there is empty element after last delimiter so it sees only "a" and "b" as tokens, not "a", "b", ""。事实上,与您对我的评论的回复一致,它看到了标记“a”、“b”、“”。不仅仅是“a”和“b”。
    • @KlitosKyriacou 当我们了解分隔符与分隔符和终止符不同(这些不是同义词)时,更容易理解 Scanner。简而言之,如果我们有像,a,, 这样的文本,那么如果, 是分隔符,那么我们有两个元素"a"""。如果 , 是分隔符,我们有 3 个元素 "" "a"""。如果, 是终止符,我们有 4 个元素:"""a"""""。在 Scanner 中,我们使用分隔符。是的,Scanner 与 split() 不同,因为它不会删除所有尾随空字符串,而只会删除最后一个分隔符之后的一个。
    • @KlitosKyriacou 更新:我在之前的评论中混合了终止符和分隔符。所以对于,a,,,如果, 是终止符,我们将得到3 个元素,如果是分隔符,则得到4 个元素。
    【解决方案2】:

    我阅读了一些Scanner documentation,其中写道:

    根据分隔模式的类型,可能会返回空标记。例如,模式 "\s+" 将不返回空标记,因为它匹配分隔符的多个实例。定界模式“\s”可能会返回空标记,因为它一次只传递一个空格。

    观察到行为的原因是默认分隔符,即 \\p{javaWhitespace}+,正如您在 Scanner.WHITESPACE_PATTERN (code from OpenJDK) 和 Scanner.reset() 中看到的那样(它将分隔符重置为该模式)。由于+,它将您的整个输入匹配为一个分隔符。

    如果您通过在末尾添加 + 来更改自定义分隔符,它们也会将连续的空格视为一个分隔符。

    【讨论】:

    • +1,但您能否编辑指向documentation 的链接?这既可以作为您的报价来源,也可以帮助人们阅读更多内容。
    • 特别是,请您链接到Scanner.WHITESPACE_PATTERN 的文档。
    • @Justastudent 抱歉,我认为您在我点击提交之前进行了编辑,因此我覆盖了您的更改。我又添加了链接。
    • 是的,我就是这么想的。不用担心!如果您仍在编辑,我发表了评论,可能应该首先这样做。谢谢:-)
    【解决方案3】:

    您尝试的两种空白模式都不匹配默认分隔符"\\p{javaWhitespace}+"documentation 并没有说明这一点:它只是说“扫描器使用分隔符模式将其输入分解为标记,默认情况下匹配空格。”通俗地说,“空白”意味着任意数量的连续空白字符。

    默认分隔符的最终规范仅在Scanner.reset()的文档中说明,它将分隔符重置为默认值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 1970-01-01
      • 2021-01-10
      • 2019-03-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多