使用next()或nextFoo()后,Scanner跳过了nextLine()吗?

943

我正在使用Scanner中的nextInt()nextLine()方法来读取输入。

代码如下:

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
System.out.println("Enter 1st string"); 
String string1 = input.nextLine(); // Read 1st string (this is skipped)
System.out.println("Enter 2nd string");
String string2 = input.nextLine(); // Read 2nd string (this appears right after reading numerical value)
问题在于输入数字值后,第一个input.nextLine()被跳过执行了,而第二个input.nextLine()被执行了,因此我的输出看起来像这样:

问题是在输入数值后,第一个 input.nextLine() 被跳过了,而第二个 input.nextLine() 被执行了,导致我的输出结果如下:

Enter numerical value
3   // This is my input
Enter 1st string    // The program is supposed to stop here and wait for my input, but is skipped
Enter 2nd string    // ...and this line is executed and waits for my input

我测试了我的应用程序,看起来问题出在使用input.nextInt()上。如果我删除它,那么string1 = input.nextLine()string2 = input.nextLine()就会像我希望的那样执行。


13
你也可以像我一样使用 BufferedReader :) 我不在乎它是否过时,它一直都为我工作并且将来也会。此外,掌握 BufferedReader 在其他地方也很有用。我只是不喜欢 Scanner。 - Cruncher
25个回答

1149

这是因为 Scanner.nextInt 方法不会读取通过“Enter”键创建的输入中的 换行符,因此调用Scanner.nextLine方法在读取该换行符后返回。

当使用Scanner.next()或任何Scanner.nextFoo方法(除了nextLine本身)后再使用Scanner.nextLine时,您将遇到类似的行为。

解决方法:

  • 在每个Scanner.nextIntScanner.nextFoo之后放置一个Scanner.nextLine调用,以消耗包括换行符在内的该行剩余部分。

    int option = input.nextInt();
    input.nextLine();  // Consume newline left-over
    String str1 = input.nextLine();
    
  • 或者更好的方法是,通过Scanner.nextLine读取输入,并将输入转换为所需的正确格式。例如,您可以使用Integer.parseInt(String)方法将其转换为整数。

  • int option = 0;
    try {
        option = Integer.parseInt(input.nextLine());
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
    String str1 = input.nextLine();
    

9
你需要使用try-catch,因为当Integer.parseInt传递无效参数时会抛出NumberFormatException异常。以后你会学习有关异常处理的知识。例如:- Integer.parseInt("abc")。你不希望"abc"被转换为整数,对吗? - Rohit Jain
3
在上述情况下,你的代码将在该点停止,无法继续执行。使用异常处理,你可以处理这种类型的条件。 - Rohit Jain
4
后者更好到哪个程度?据我所知,Scanner#nextInt() 在查找正确的整数时更加宽容,允许使用组逗号和区域设置前缀和后缀。Integer#parseInt() 仅允许数字和小数点以及可选符号。 - Mordechai
6
我个人更喜欢先使用Scanner#hasNextFoo检查,而不是使用try-catch,但那样也可以。 - MercyBeaucou
1
使用ParseDouble有一个新问题,即nextDouble使用十进制的区域配置(.或,),但是Parsedouble始终接收美国十进制(.)。 - olmerg
显示剩余7条评论

275

问题出在 input.nextInt() 方法上;它只读取 int 值。因此,当你使用 input.nextLine() 继续读取时,你会收到“\n”回车键。所以为了跳过这个,你需要添加 input.nextLine()

试试这样做:

System.out.print("Insert a number: ");
int number = input.nextInt();
input.nextLine(); // This line you have to add (It consumes the \n character)
System.out.print("Text1: ");
String text1 = input.nextLine();
System.out.print("Text2: ");
String text2 = input.nextLine();

2
FYI:合并自http://stackoverflow.com/questions/7056749/scanner-issue-when-using-nextline-after-nextxxx - Shog9
1
如果是这样,为什么在使用Scanner.nextInt()之后调用Scanner.next()时我们不会遇到同样的问题呢?为什么我在Scanner.next()中没有得到一个空字符串? - Tushar

97
这是因为当你输入一个数字然后按下Enter键时,input.nextInt() 只消耗数字,而不是“行结束符”。 当执行input.nextLine()时,它仍然从第一个输入中缓冲区中消耗了“行结束符”。
相反地,在input.nextInt()之后立即使用input.nextLine()

9
@Victor,这不是bug,它的工作方式如规定的那样。但你可以说,应该有一种简单的方法来告诉API也消耗目标输入后面的任何空格。 - Bohemian
1
我明白了。谢谢@Bohemian,这正是我所争论的。我认为应该进行更改,也许你可以指导我在下一次JCP中建议这个“问题”应该放在哪里。 - Victor
@victor 你可以通过报告错误或请求功能页面请求(并了解如何贡献代码)某个功能。 - Bohemian
FYI:合并自http://stackoverflow.com/questions/7056749/scanner-issue-when-using-nextline-after-nextxxx - Shog9
如果是这样,为什么在Scanner.nextInt()后使用Scanner.next()调用时我们不会遇到相同的问题呢?为什么我在Scanner.next()中没有得到一个空字符串? - Tushar

53

关于使用java.util.Scanner的问题似乎有很多疑问。我认为一种更易读/惯用的解决方案是在调用nextInt()后调用scanner.skip("[\r\n]+")以删除任何换行符。

编辑:正如@PatrickParker在下面指出的那样,如果用户在数字后输入任何空格,则会导致无限循环。请参阅其答案以获取更好的模式用于skip:https://dev59.com/q_z2s4cB2Jgan1zntq6l#42471816


FYI:合并自http://stackoverflow.com/questions/7056749/scanner-issue-when-using-nextline-after-nextxxx的问题。 - Shog9
我知道我们如何清除缓冲区中的数据,但是在这种情况下,请帮我解决这个问题:http://stackoverflow.com/questions/33585314/having-issues-with-scanner - Twitter khuong291
1
请注意,如果用户在数字输入后键入任何制表符或空格,将会导致无限循环。请参见我的答案,以获取更好的跳过方法:https://dev59.com/q_z2s4cB2Jgan1zntq6l#42471816 - Patrick Parker
@PatrickParker:确实会导致无限循环!谢谢,我已经编辑了答案并链接到你的回答。 - Denis Tulskiy

43

简述:
nextLine() 在以下两种情况下是安全的:(a) 它是第一次读取指令,(b) 上一个读取指令也是 nextLine()

如果您不确定以上哪种情况为真,可以在调用 scanner.nextLine() 之前使用 scanner.skip("\\R?") ,因为像 next() nextInt() 这样的调用会留下由回车键创建的潜在行分隔符,这将影响 nextLine() 的结果。 .skip("\\R?") 将允许我们消耗这个不必要的行分隔符。

skip 使用正则表达式,其中

  • \R代表换行符。
  • ?将使\R变为可选项,这将防止skip方法在以下情况下等待匹配序列:
    • 当到达仍然打开的数据源(如System.in、来自套接字的输入流等)的末尾时;
  • 在以下情况下抛出java.util.NoSuchElementException异常:
    • 数据源已终止/关闭,
    • 或现有数据与我们要跳过的内容不匹配。

你需要知道的事情:

  • 文本中包含几行文字,也包含行之间的非可打印字符(我们称之为行分隔符),例如

  • 回车符(CR - 在字符串字面值中表示为"\r"

  • 换行符(LF - 在字符串字面值中表示为"\n"

  • 当您从控制台读取数据时,它允许用户输入响应,并在完成后需要以某种方式确认该事实。为此,用户需要在键盘上按下“enter”/“return”键。

重要的是,除了确保将用户数据放置到标准输入(由System.in表示,由Scanner读取)之外,此键还会发送操作系统相关的行分隔符(例如对于Windows \r\n)。

因此,当您询问用户像age这样的值时,用户键入42并按Enter键,标准输入将包含"42\r\n"

问题

Scanner#nextInt(以及其他Scanner#nextType方法)不允许Scanner“消耗”这些行分隔符。它将从System.in读取它们(否则Scanner怎么知道用户输入的数字没有更多代表age值的空格?),并将其从标准输入中删除,但是它也会在内部“缓存”这些行分隔符。需要记住的是,所有Scanner方法始终从缓存文本开始扫描。

现在,Scanner#nextLine()只需收集并返回所有字符,直到找到行分隔符(或流结束)。但是,由于在从控制台读取数字后立即在Scanner的缓存中找到行分隔符,因此它返回空字符串,这意味着Scanner无法在这些行分隔符(或流结束)之前找到任何字符。
顺便说一句,nextLine还会“消耗”这些行分隔符。

解决方案

因此,当您想要询问数字,然后询问整个行而避免空字符串成为nextLine的结果时,请执行以下操作:

  • 使用nextInt从扫描器缓存中消耗掉行分隔符,可以通过调用nextLine来完成,
  • 或者更易读的方式是通过调用skip("\\R")skip("\r\n|\r|\n")让扫描器跳过与行分隔符匹配的部分(有关\R的更多信息:https://dev59.com/OHRB5IYBdhLWcg3w9L3n#31060125
  • 不要使用nextInt (也不要使用任何nextTYPE方法),而是使用nextLine逐行读取整个数据,并从每行解析数字(假设一行只包含一个数字)到正确的类型,如int,使用Integer.parseInt

顺便提一下:Scanner#nextType方法可以跳过分隔符(默认为所有空格,如制表符、换行符),包括扫描器缓存的那些分隔符,直到找到下一个非分隔符值(标记)。由于这个特性,对于像"42\r\n\r\n321\r\n\r\n\r\nfoobar"的输入,代码可以进行处理。

int num1 = sc.nextInt();
int num2 = sc.nextInt();
String name = sc.next();

将能够正确地分配 num1=42 num2=321 name=foobar


38
它这样做是因为input.nextInt();没有捕获换行符。你可以像其他人建议的那样,在下面添加input.nextLine();
或者你可以像C#一样将nextLine解析为整数,如下所示:
int number = Integer.parseInt(input.nextLine()); 

这样做同样有效,而且可以节省一行代码。


请注意:此内容已从http://stackoverflow.com/questions/7056749/scanner-issue-when-using-nextline-after-nextxxx合并。 - Shog9
1
你必须在这里使用try-catch。如果输入不是一个数字会发生什么?需要处理NumberFormatException。 - Arundev
这假设你尝试处理的每一行都只有一个 int 标记。 - cellepo
如果是这样,为什么在使用Scanner.nextInt()之后调用Scanner.next()时我们不会遇到同样的问题呢?为什么我在Scanner.next()中不会得到一个空字符串? - Tushar
@Tushar 我希望我还记得,自从我在2013年写下那个答案以来,我就没有碰过Java了。 - Electric Coffee

19

不要使用 input.nextLine(),而是使用 input.next(),这应该可以解决问题。

修改后的代码:


public static Scanner input = new Scanner(System.in);

public static void main(String[] args)
{
    System.out.print("Insert a number: ");
    int number = input.nextInt();
    System.out.print("Text1: ");
    String text1 = input.next();
    System.out.print("Text2: ");
    String text2 = input.next();
}

FYI:合并自http://stackoverflow.com/questions/7056749/scanner-issue-when-using-nextline-after-nextxxx - Shog9

16

如果您想同时读取字符串和整数,一种解决方案是使用两个扫描器:

Scanner stringScanner = new Scanner(System.in);
Scanner intScanner = new Scanner(System.in);

intScanner.nextInt();
String s = stringScanner.nextLine(); // unaffected by previous nextInt()
System.out.println(s);

intScanner.close();
stringScanner.close();

请不要这样做。每个输入流只使用一个扫描器。另外,当您执行intScanner.close();时,您会关闭stringScanner和整个System.in,导致其余的执行无法继续。 - aran

14

如果你想快速扫描输入,而不会混淆 Scanner 类的 nextLine() 方法,请使用自定义输入扫描器。

代码:

class ScanReader {
/**
* @author Nikunj Khokhar
*/
    private byte[] buf = new byte[4 * 1024];
    private int index;
    private BufferedInputStream in;
    private int total;

    public ScanReader(InputStream inputStream) {
        in = new BufferedInputStream(inputStream);
    }

    private int scan() throws IOException {
        if (index >= total) {
            index = 0;
            total = in.read(buf);
            if (total <= 0) return -1;
        }
        return buf[index++];
    }
    public char scanChar(){
        int c=scan();
        while (isWhiteSpace(c))c=scan();
        return (char)c;
    }


    public int scanInt() throws IOException {
        int integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public String scanString() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        StringBuilder res = new StringBuilder();
        do {
            res.appendCodePoint(c);
            c = scan();
        } while (!isWhiteSpace(c));
        return res.toString();
    }

    private boolean isWhiteSpace(int n) {
        if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
        else return false;
    }

    public long scanLong() throws IOException {
        long integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public void scanLong(long[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanLong();
    }

    public void scanInt(int[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanInt();
    }

    public double scanDouble() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = scan();
        }
        double res = 0;
        while (!isWhiteSpace(c) && c != '.') {
            if (c == 'e' || c == 'E') {
                return res * Math.pow(10, scanInt());
            }
            res *= 10;
            res += c - '0';
            c = scan();
        }
        if (c == '.') {
            c = scan();
            double m = 1;
            while (!isWhiteSpace(c)) {
                if (c == 'e' || c == 'E') {
                    return res * Math.pow(10, scanInt());
                }
                m /= 10;
                res += (c - '0') * m;
                c = scan();
            }
        }
        return res * sgn;
    }

}

优点:

  • 比BufferReader更快地扫描输入
  • 减少时间复杂度
  • 为每个下一个输入刷新缓冲区

方法:

  • scanChar() - 扫描单个字符
  • scanInt() - 扫描整数值
  • scanLong() - 扫描长整数值
  • scanString() - 扫描字符串值
  • scanDouble() - 扫描双精度浮点数值
  • scanInt(int[] array) - 扫描完整数组(整数)
  • scanLong(long[] array) - 扫描完整数组(长整数)

用法:

  1. 将给定代码复制到您的Java代码下方。
  2. 初始化给定类的对象。

ScanReader sc = new ScanReader(System.in); 3. 导入必要的类:

import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; 4. 从主方法中抛出IOException以处理异常。 5. 使用提供的方法。 6. 尽情享受。

示例:

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
class Main{
    public static void main(String... as) throws IOException{
        ScanReader sc = new ScanReader(System.in);
        int a=sc.scanInt();
        System.out.println(a);
    }
}
class ScanReader....

14
为了避免这个问题,使用nextInt();之后立即使用nextLine();,因为它有助于清除缓冲区。当你按下ENTER时,nextInt();不会捕捉到新的一行,因此稍后跳过Scanner代码。
Scanner scanner =  new Scanner(System.in);
int option = scanner.nextInt();
scanner.nextLine(); //clearing the buffer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接