Scanner: примеры (JAVA)
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.
Расширенные и редкие сценарии использования
Парсинг чисел с другой системой счисления и локалью:
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 для чтения произвольных блоков, включая многострочные записи:
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 для поиска по сложному шаблону в большом тексте:
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 для разбора структурированного текста (часто для быстрых утилит):
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):
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-ом при необходимости - уменьшает накладные расходы и повышает гибкость.