Сканер пропускає nextLine () після використання next () або nextFoo ()?


684

Я використовую 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()виконується, так що мій вихід виглядає приблизно так:

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()виконуються так, як я хочу, щоб вони були.


5
Або ти можеш бути схожим на мене і використовувати BufferedReader :) Мені все одно, чи це стара школа, вона завжди працювала і завжди буде працювати для мене. Також знання BufferedReader мають застосування в інших місцях. Мені просто не подобається Сканер.
Cruncher

Відповіді:


831

Це тому, що Scanner.nextIntметод не зчитує символ нового рядка у вашому введенні, створений натисканням клавіші "Enter", і тому дзвінок Scanner.nextLineповертається після читання нового рядка .

Ви будете стикатися з подібною поведінкою, коли будете користуватися Scanner.nextLineпісля Scanner.next()чи будь-яким Scanner.nextFooметодом (крім nextLineсамого себе).

Обхід:

  • Або поставити Scanner.nextLineвиклик після кожного Scanner.nextIntабо Scanner.nextFooспоживати решту цієї лінії , включаючи символ нового рядка

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

4
@blekione. Ви повинні використовувати try-catch, тому що Integer.parseIntкидає, NumberFormatExceptionколи йому передається недійсний аргумент. Про виняток ви дізнаєтесь пізніше. Для EG: - Integer.parseInt("abc"). Ви не хочете, щоб "abc" перетворився на int правильно?
Rohit Jain

3
@blekione. Отже, у вищенаведеному випадку ваш код зупиниться в цей момент, і ви не зможете продовжувати виконання. Завдяки обробці винятків ви можете впоратися з такими умовами.
Rohit Jain

4
Наскільки останній кращий? Сканер AFAIK # nextInt () набагато поблажливіший у пошуку правильних вводів, дозволяючи групувати коми та локальні префікси та суфікси. Integer # parseInt () дозволяє лише цифри і десяткові крапки плюс додатковий знак.
Мордехай

5
Я особисто віддаю перевагу Scanner#hasNextFooчеку заздалегідь, а не пробі, але це теж працює.
MildlyMilquetoast

1
Використовувати ParseDouble є нова проблема, що nextDouble використовує регіональну конфігурацію десяткових (. Або,), але Parsedouble завжди отримує десяткові значення (.).
olmerg

209

Проблема полягає у методі input.nextInt () - він зчитує лише значення int. Отже, продовжуючи читати за допомогою input.nextLine (), ви отримуєте клавішу "\ n" Enter. Отже, щоб пропустити це, ви повинні додати 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();

Умм здається рішенням, що робить пропущену лінію не використаною nextLine, але мені все ж потрібне пояснення такої поведінки
Eng.Fouad


Питання: задано 27 жовтня 12.12 в 16:37. відповів 14 серпня 1111 о 12:24. Як це можливо навіть?
Апоорв Патне

79

Це тому, що коли ви вводите число, тоді натискаєте Enter, input.nextInt()споживає лише число, а не "кінець рядка". При input.nextLine()виконанні він споживає "кінець рядка", що все ще знаходиться в буфері з першого вводу.

Замість цього використовуйте input.nextLine()відразу післяinput.nextInt()


7
@Victor це не помилка. він працює, як зазначено. Ви можете заперечити, що повинен бути простий спосіб сказати api, щоб він також споживав пробіл після цільового введення.
богем

Розумію. дякую @Bohemian, що саме про це я сперечаюся. Я думаю, що це слід змінити, можливо, ви можете вказати мені, де запропонувати це "питання" у наступному JCP.
Віктор

@victor Ви можете надіслати запит (і дізнатися, як внести код до функції) за допомогою Повідомити про помилку чи надіслати запит на сторінку функції .
богем


48

Здається, існує багато питань щодо цього питання java.util.Scanner. Я думаю, що більш читальним / ідіоматичним рішенням було б закликати scanner.skip("[\r\n]+")скидати будь-які символи нового рядка після виклику nextInt().

EDIT: як @PatrickParker зазначив нижче, це призведе до нескінченного циклу, якщо користувач введе пробіл після числа. Дивіться їх відповідь для кращого шаблону для пропуску: https://stackoverflow.com/a/42471816/143585



Я знаю, що ми робимо для видалення даних у буфері, але в цьому випадку, будь ласка, допоможіть мені в цьому: stackoverflow.com/questions/33585314/having-isissue-with-scanner
Khuong

1
FYI, це призведе до нескінченного циклу, коли користувач вводить будь-які вкладки або пробіли після числового введення. Дивіться мою відповідь тут для кращого пропуску: stackoverflow.com/a/42471816/7098259
Патрік Паркер,

@PatrickParker: це дійсно викликає нескінченний цикл! Дякуємо, відредагували відповідь, щоб посилатися на вашу.
Денис Тульський

33

Це робиться тому, input.nextInt();що не захоплює новий рядок. ви можете зробити так, як інші, запропоновані, додавши input.nextLine();під них.
Можна також зробити стиль C # і проаналізувати nextLine на ціле число, як-от так:

int number = Integer.parseInt(input.nextLine()); 

Це працює так само добре, і це економить рядок коду.



1
тут вам доведеться використовувати спробу лову. Що буде, якщо вхід не буде числом. Тут потрібно опрацювати NumberFormatException.
Арундев

Це передбачає, що у кожному рядку, для якого ви намагаєтеся, є лише один ток маркера.
cellepo

25

TL; DR Використовуйте scanner.skip("\\R")перед кожним scanner.newLine()викликом, який виконується після:

  • scanner.next()
  • scanner.next*TYPE*() метод.

Що потрібно знати:

  • Текст, який представляє кілька рядків, також містить символи, що не друкуються між рядками (ми їх називаємо роздільниками рядків)

    • повернення каретки (CR - у рядках-рядках, представлених як "\r")
    • рядок (LF - у рядкових літералах, представлених як "\n")
  • коли ви читаєте дані з консолі, це дозволяє користувачеві вводити свою відповідь, і коли він завершиться, йому потрібно якось підтвердити цей факт. Для цього користувачеві потрібно натиснути клавішу «ввести» / «повернути» на клавіатурі.

    Важливо те, що ця клавіша, крім забезпечення розміщення даних користувачів на стандартний вхід (представлений System.inяким зчитується Scanner), також надсилає \r\nпісля нього роздільні лінії, що залежать від ОС (як для Windows ).

    Отже, коли ви запитуєте у користувача такого значення, як ageвводити типи користувачів 42 та натискання, стандартний ввід міститиме "42\r\n".

Проблема

Scanner#nextInt(та інші методи) не дозволяє Сканеру споживати ці роздільники рядків. Він прочитає їх з (як інакше Сканер знав би, що у користувача немає більше цифр, які представляють значення, ніж обличчя білого проміжку?), Який видалить їх зі стандартного вводу, але він також буде кешувати ці роздільники рядків всередині . Що нам потрібно пам’ятати, це те, що всі методи Scanner завжди скануються, починаючи з кешованого тексту. Scanner#nextTypeSystem.inage

Тепер Scanner#nextLine()просто збирає та повертає всі символи, поки не знайде роздільники рядків (або кінець потоку). Але оскільки роздільники рядків після зчитування номера з консолі знаходяться негайно в кеші сканера, він повертає порожню рядок, тобто сканер не зміг знайти жодного символу перед цими роздільниками рядків (або в кінці потоку).
BTW nextLineтакож споживає ці роздільники ліній.

Рішення

Так що, якщо ви хочете , щоб попросити номера , а потім по всій лінії, уникаючи при цьому , що порожній рядок як результат nextLine, або

  • споживати роздільник ліній, залишений nextIntіз кешу сканерів
    • дзвінок nextLine,
    • чи більш читаним способом буде IMO , зателефонувавши skip("\\R")або skip("\r\n|\r|\n")дозволити Сканеру пропустити частину, узгоджену роздільником рядків (детальніше про \R: https://stackoverflow.com/a/31060125 )
  • взагалі не використовувати nextInt(ні next, або будь-які nextTYPEметоди). Натомість читайте цілі дані по черзі, використовуючи nextLineта аналізуйте номери з кожного рядка (якщо один рядок містить лише одне число) до відповідного типу, наприклад intчерез Integer.parseInt.

BTW : методи можуть пропускати роздільники (за замовчуванням всі пробіли, такі як вкладки, роздільники рядків), включаючи кешовані сканером, доки вони не знайдуть наступне значення, що не має роздільника (маркер). Завдяки цьому для введення, як код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.


14

Замість 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();
}


13

Якщо ви хочете прочитати як рядки, так і ints, рішенням є використання двох сканерів:

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

1
Це я вже деякий час шукав. Розумний хід!
Ліам Вілсон

1
@BdLearner scanner.nextLine()має працювати.
Андре Валенті

11

Якщо ви хочете швидко сканувати введення даних, не плутаючись у методі 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 () - сканувати значення String
  • scanDouble () - сканування Подвійне значення
  • scanInt (масив int []) - сканує повний масив (Integer)
  • scanLong (довгий [] масив) - сканує повний масив (довгий)

Використання:

  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....

10

Щоб уникнути проблеми, використовуйте nextLine();відразу після цього, nextInt();оскільки це допомагає очистити буфер. При натисканні захоплює новий рядок і , отже, пропускає код пізніше.ENTERnextInt();Scanner

Scanner scanner =  new Scanner(System.in);
int option = scanner.nextInt();
scanner.nextLine(); //clearing the buffer

9

sc.nextLine()краще порівняно з розбором вхідних даних. Тому що продуктивність мудра, це буде добре.


5

Я думаю, я досить спізнився на вечірку ..

Як було сказано раніше, дзвінок input.nextLine()після отримання вашої int значення вирішить вашу проблему. Причина, по якій ваш код не працював, полягала в тому, що нічого не можна було зберігати з вашого вводу (куди ви ввели int) string1. Я просто пролити трохи більше світла на всю тему.

Розглянемо nextLine () як непарне серед методів nextFoo () класу Scanner. Візьмемо короткий приклад. Скажімо, у нас є два рядки коду, як наведені нижче:

int firstNumber = input.nextInt();
int secondNumber = input.nextInt();

Якщо ми введемо значення нижче (як єдиний рядок введення)

54 234

Значення нашої firstNumberта secondNumberзмінної стають відповідно 54 та 234. Причина, чому це працює таким чином, полягає в тому, що новий канал рядка ( тобто \ n ) НЕ автоматично генерується, коли метод nextInt () приймає значення. Він просто бере "наступний int" і рухається далі. Це те ж саме для решти методів nextFoo (), крім nextLine ().

nextLine () генерує новий канал рядка одразу після прийняття значення; це те, що означає @RohitJain, кажучи, що новий канал каналу "споживається".

Нарешті, наступний () метод просто приймає найближчий рядок без створення нового рядка; це робить це переважним методом для взяття окремих рядків в одному і тому ж рядку.

Я сподіваюся, що це допоможе .. Веселого кодування!


що таке "нова стрічка каналів"?
Арчер

@Abcd Новий канал каналу в основному означає "починаючи з нового рядка".
Таслім Осені

1
Цей приклад "54 234" дійсно прояснює речі.
Алонсо дель Арте

3

Використовуйте 2 об'єкти сканера замість одного

Scanner input = new Scanner(System.in);
System.out.println("Enter numerical value");    
int option;
Scanner input2 = new Scanner(System.in);
option = input2.nextInt();

2
public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int i = scan.nextInt();
        scan.nextLine();
        double d = scan.nextDouble();
        scan.nextLine();
        String s = scan.nextLine();

        System.out.println("String: " + s);
        System.out.println("Double: " + d);
        System.out.println("Int: " + i);
    }

Якщо ви використовуєте метод nextLine () безпосередньо після методу nextInt (), згадайте, що nextInt () читає цілі лексеми; через це останній символ нового рядка для цього рядка з цілим числом вводиться до черги у вхідному буфері, а наступний nextLine () буде читати залишок цілого рядка (який порожній).
Neeraj Gahlawat

2

якщо я очікую не пустий вхід

public static Function<Scanner,String> scanLine = (scan -> {
    String s = scan.nextLine();
    return( s.length() == 0 ? scan.nextLine() : s );
  });


використаний у наведеному вище прикладі:

System.out.println("Enter numerical value");    
int option = input.nextInt(); // read numerical value from input

System.out.println("Enter 1st string"); 
String string1 = scanLine.apply( input ); // read 1st string
System.out.println("Enter 2nd string");
String string2 = scanLine.apply( input ); // read 2nd string

2

В одному зі моїх випадків у мене був сценарій зчитування значення рядка, якому передувало пара цілих значень . Мені довелося використовувати цикл " for / while ", щоб прочитати значення. І жодна з перерахованих вище пропозицій не спрацювала в цьому випадку.

Використання input.next()замість input.nextLine()виправленої проблеми. Сподіваюся, це може бути корисним для тих, хто має справу зі схожим сценарієм.


2

Використовуючи цей код, він вирішить вашу проблему.

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
input.nextLine();
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)

2

Оскільки nextXXX()методи не читаються newline, за винятком nextLine(). Ми можемо пропустити newlineпісля прочитання будь-яке non-stringзначення ( intв даному випадку), використовуючи наступне scanner.skip():

Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(x);
double y = sc.nextDouble();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(y);
char z = sc.next().charAt(0);
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(z);
String hello = sc.nextLine();
System.out.println(hello);
float tt = sc.nextFloat();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(tt);

0

Чому б не використовувати новий сканер для кожного читання? Як і нижче. При такому підході ви не зіткнетеся зі своєю проблемою.

int i = new Scanner(System.in).nextInt();

3
Але тоді вам доведеться закрити, Scannerщоб запобігти витоку пам'яті. Марнування часу?
TheCoffeeCup

1
Чи GC не піклується про це, якщо сканер обгорнуто методом? Як: String getInput () {повернути новий сканер (System.in) .nextLine ()};
Тобіас Йоханссон

Це абсолютно неправильно. nextInt()не буде споживати новий рядок, незалежно від того, чи є він у "новому" Scannerабо вже використаному.
Dawood ibn Kareem
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.