Scanner: примеры (JAVA)

Класс Scanner в Java: назначение и примеры
Раздел: Ввод-вывод (I/O) консольный
Scanner(InputStream source)

Общее описание и сигнатуры

Класс Scanner (java.util.Scanner) предназначен для разбиения входного потока на токены и преобразования их в примитивные типы или строки. Часто используется для простого парсинга текстового ввода: с клавиатуры (System.in), из файла, из строки или из сетевого потока.

Конструкторы (основные):

  • Scanner(InputStream source) - чтение из потока байтов (обычно System.in).
  • Scanner(File source) - чтение из файла.
  • Scanner(Path source) - чтение из пути (Java 7+).
  • Scanner(Readable source) - общая версия для Readable.
  • Scanner(String source) - парсинг из строки.

Частые методы и их поведение:

  • boolean hasNext() - есть ли следующий токен (по текущему разделителю); возвращает true/false.
  • String next() - возвращает следующий токен как строку; бросает NoSuchElementException, если токенов нет.
  • String nextLine() - возвращает остаток текущей строки до разделителя конца строки и перемещает указатель на следующую строку; если строк нет, бросает NoSuchElementException.
  • int nextInt() и int nextInt(int radix) - парсинг целого; при несоответствии формата возникает InputMismatchException.
  • long nextLong(), double nextDouble() и другие методы для примитивов - работают аналогично.
  • boolean hasNextInt(), hasNextDouble() и подобные - проверяют, следующий токен может ли быть спарсен в указанный тип.
  • String next(Pattern pattern) и String next(String pattern) - поиск следующего токена по регулярному выражению.
  • String findWithinHorizon(Pattern pattern, int horizon) и String findInLine(Pattern pattern) - более гибкий поиск по шаблону.
  • Scanner useDelimiter(Pattern pattern) и Scanner useDelimiter(String pattern) - изменение разделителя токенов (по умолчанию - пробельные символы).
  • Scanner useLocale(Locale locale) - установка локали для разбора чисел (например, десятичный разделитель запятая в некоторых локалях).
  • Scanner useRadix(int radix) и int radix() - установка и получение системы счисления для методов nextInt/nextLong при отсутствии явного radix.
  • void close() - закрывает сканер и освобождает ресурсы; при закрытии закрывается и источник, если он является Closeable.

Типичные возвращаемые значения: примитивы (int, long, double и т. п.), строки, boolean для проверочных методов. При ошибках бросаются стандартные unchecked-исключения: InputMismatchException, NoSuchElementException, IllegalStateException.

Короткие примеры использования

Чтение токенов из строки:

String input = "Alice 25 3.14";
Scanner sc = new Scanner(input);
String name = sc.next();
int age = sc.nextInt();
double pi = sc.nextDouble();
sc.close();
System.out.println(name + ", " + age + ", " + pi);
Alice, 25, 3.14

Чтение построчно и проблема с nextLine после nextInt:

String input = "42\nHello world\n";
Scanner sc = new Scanner(input);
int num = sc.nextInt();
String line = sc.nextLine(); // возвращает пустую строку, так как остался символ конца строки
String next = sc.nextLine();
sc.close();
System.out.println("num=" + num);
System.out.println("line1='" + line + "'");
System.out.println("line2='" + next + "'");
num=42
line1=''
line2='Hello world'

Парсинг CSV с кастомным разделителем:

String csv = "a,b,c\n1,2,3";
Scanner sc = new Scanner(csv).useDelimiter(",|\r?\n");
while (sc.hasNext()) System.out.println(sc.next());
sc.close();
a
b
c
1
2
3

Чтение из файла:

try (Scanner sc = new Scanner(new File("data.txt"), "UTF-8")) {
  while (sc.hasNextLine()) {
    System.out.println(sc.nextLine());
  }
}
(вывод содержимого data.txt)

Альтернативы внутри Java

  • BufferedReader: эффективное чтение построчно через readLine, предпочтительнее при работе с большими файлами или при необходимости контролировать ввод символов и кодировку. Возвращает строки, не парсит на типы.
  • Console: удобен для интерактивного ввода в консоли (readLine, readPassword), работает в интерактивной сессии, но может быть недоступен в IDE.
  • StreamTokenizer: более низкоуровневый парсер токенов с поддержкой чисел и слов, полезен при сложном синтаксическом разборе.
  • java.nio.file.Files.lines: возвращает Stream для построчной обработки с возможностью параллельной и ленивой обработки; лучше для потоковой обработки больших файлов с функциональными операциями.
  • Pattern и Matcher: прямой парсинг через регулярные выражения, даёт полный контроль над шаблонами, но требует больше кода.

Выбор зависит от задачи: для простого интерактивного парсинга Scanner удобнее, для производительных или тонких задач по работе с кодировкой и потоками предпочтительнее BufferedReader или NIO.

Аналоги в других языках и отличия

PHP:

$input = "1 2 3";
$tokens = preg_split('/\s+/', trim($input));
print_r($tokens);
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)

JavaScript (Node.js):

const input = '42 hello 3.14'.split(/\s+/);
console.log(input);
[ '42', 'hello', '3.14' ]

Python:

s = "Alice 30 2.71"
parts = s.split()
name = parts[0]
age = int(parts[1])
print(name, age)
Alice 30

C#:

string s = "1 2 3";
var parts = s.Split(new[]{' ', '\t', '\n'}, StringSplitOptions.RemoveEmptyEntries);
foreach (var p in parts) Console.WriteLine(p);
1
2
3

Go:

scanner := bufio.NewScanner(strings.NewReader("a b c"))
for scanner.Scan() {
    fmt.Println(scanner.Text())
}
// По умолчанию Scanner разбирает по строкам, можно задать Split на слова
a b c (в зависимости от Split)

Lua:

local s = "10 20 30"
for num in s:gmatch("%S+") do
  print(num)
end
10
20
30

Краткие замечания: в большинстве языков базовые операции по разбиению и преобразованию типов реализуются через split/gmatch/scanf-подобные функции. Java Scanner объединяет токенизацию и преобразование типов, но медленнее низкоуровневых средств и менее гибок по сравнению с регулярными выражениями или потоковыми API.

Типичные ошибки и их проявления

  • mix nextInt() и nextLine(): после nextInt остаётся символ конца строки, поэтому следующий nextLine вернёт пустую строку. Пример выше в разделе примеров.
  • InputMismatchException: попытка вызвать nextInt при токене, не являющемся числом.
  • NoSuchElementException: вызов next* при отсутствии доступных токенов.
  • IllegalStateException: попытка использовать закрытый Scanner.
  • Закрытие System.in: при вызове sc.close() для Scanner, созданного на System.in, поток System.in закрывается; дальнейшие попытки чтения из консоли вызовут ошибки. Рекомендуется не закрывать Scanner, если нужен дальнейший ввод из System.in.
  • Проблемы с локалью: при парсинге дробных чисел десятичный разделитель зависит от Locale; nextDouble может ожидать запятую вместо точки и наоборот. Решение - явно задавать useLocale.
  • Производительность: Scanner медленнее BufferedReader и парсинга вручную; для большого объёма данных рекомендуется использовать более быстрые подходы.

Пример InputMismatch:

Scanner sc = new Scanner("abc");
int x = sc.nextInt(); // бросит InputMismatchException
Exception in thread "main" java.util.InputMismatchException
    at java.base/java.util.Scanner.throwFor(Scanner.java:939)
    ...

Изменения в последних версиях Java

API класса Scanner оставался стабильным в течение последних версий Java. Небольшие улучшения и исправления производительности появлялись в минорных релизах, но значительных изменений в публичных сигнатурах не было. Рекомендуется смотреть заметки к конкретной версии JVM для информации о внутренних оптимизациях. Для новых задач по обработке больших потоков чаще предлагаются альтернативы на базе NIO и потоков (Streams), а не изменения в Scanner.

Расширенные и редкие сценарии использования

Парсинг чисел с другой системой счисления и локалью:

Пример java
String s = "FF 077 0x1A";
Scanner sc = new Scanner(s);
int a = sc.nextInt(16); // явный radix для первого токена
int b = Integer.parseInt(sc.next(), 8); // альтернативный подход для второго токена
String third = sc.next();
sc.close();
System.out.println(a);
System.out.println(b);
System.out.println(third);
255
63
0x1A

Использование useDelimiter для чтения произвольных блоков, включая многострочные записи:

Пример java
String data = "--BEGIN--\nline1\nline2\n--END--\n";
Scanner sc = new Scanner(data).useDelimiter("--END--");
if (sc.hasNext()) {
  String block = sc.next();
  System.out.println(block);
}
sc.close();
--BEGIN--
line1
line2

findWithinHorizon для поиска по сложному шаблону в большом тексте:

Пример java
String text = "... id=12345 ... id=67890 ...";
Scanner sc = new Scanner(text);
String idPattern = "id=(\\d+)";
String found = sc.findWithinHorizon(idPattern, 100); // находит первое совпадение
sc.close();
System.out.println(found);
id=12345

Использование Scanner для разбора структурированного текста (часто для быстрых утилит):

Пример java
String input = "name:John age:30 city:NY";
Scanner sc = new Scanner(input).useDelimiter("\\s+|:");
while (sc.hasNext()) {
  String key = sc.next();
  String value = sc.next();
  System.out.println(key + " -> " + value);
}
sc.close();
name -> John
age -> 30
city -> NY

Чтение из сетевого потока с контролем таймаута (пример с Socket):

Пример java
Socket socket = new Socket("example.com", 80);
socket.setSoTimeout(2000); // таймаут чтения
try (Scanner sc = new Scanner(socket.getInputStream())) {
  while (sc.hasNextLine()) {
    System.out.println(sc.nextLine());
  }
}
socket.close();
(поток HTTP-ответа или исключение при таймауте)

Комбинация Scanner с потоками и параллельной обработкой: чтение файла строками через Files.lines, а парсинг отдельных строк Scanner-ом при необходимости - уменьшает накладные расходы и повышает гибкость.

джава Scanner function comments

En
Scanner A simple text scanner which can parse primitive types and strings