Класс Scanner в Java: чтение файлов, строк и разбор токенов - Вопросы
Всего: 5 вопросов
1. Как прочитать текстовый файл построчно с помощью Scanner и почему важно указать кодировку?
Как прочитать текстовый файл построчно с помощью Scanner и почему важно указать кодировку?
Создайте Scanner над Path с явной кодировкой внутри try-with-resources и вызывайте nextLine(), пока hasNextLine() возвращает true. nextLine() возвращает строку без символа перевода строки, пустая строка файла приходит как "".
try (Scanner scanner = new Scanner(Path.of("users.txt"), StandardCharsets.UTF_8)) {
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
Конструкторы без Charset берут кодировку по умолчанию. Начиная с Java 18 (JEP 400) это UTF-8 на всех платформах, а в Java 17 и раньше — кодировка ОС, например windows-1251 на русской Windows. Если кодировка не совпадает с кодировкой файла, кириллица превращается в «кракозябры» или чтение молча обрывается. Явное StandardCharsets.UTF_8 делает результат одинаковым на любой версии и машине. Конструктор Scanner(Path, Charset) появился в Java 10 и бросает проверяемое IOException.
2. Что возвращают методы tokens() и findAll() класса Scanner?
Что возвращают методы tokens() и findAll() класса Scanner?
Оба метода появились в Java 9 и связывают Scanner со Stream API. tokens() возвращает Stream<String> из оставшихся токенов (по текущему разделителю), а findAll(String regex) — Stream<MatchResult> со всеми совпадениями шаблона. Они заменяют ручной цикл while (hasNext()), когда токены нужно отфильтровать, преобразовать или посчитать.
try (Scanner scanner = new Scanner("Итого: 120 руб., 45 руб., 300 руб.")) {
int total = scanner.findAll("\\d+")
.map(MatchResult::group)
.mapToInt(Integer::parseInt)
.sum(); // 465
}
Поток и Scanner делят одну позицию чтения, поэтому, пока терминальная операция не завершилась, другие методы этого Scanner вызывать нельзя. Вызов tokens() или findAll() на закрытом Scanner бросает IllegalStateException.
3. Зачем у Scanner метод ioException()?
Зачем у Scanner метод ioException()?
Методы чтения Scanner не пробрасывают IOException, а запоминают его: при ошибке ввода-вывода hasNext() и hasNextLine() просто возвращают false, и обрыв чтения выглядит как обычный конец файла. Метод ioException() возвращает последнее проглоченное IOException или null, если ошибок не было.
Типичный случай — неверная кодировка: Scanner, созданный над файлом с UTF-8, встречает байты в windows-1251, декодер бросает MalformedInputException (подкласс IOException), и программа обрабатывает только начало файла, не сообщая об ошибке. Поэтому после цикла чтения файла проверяйте результат:
if (scanner.ioException() != null) {
throw scanner.ioException();
}
Сами методы Scanner бросают только непроверяемые исключения: InputMismatchException, NoSuchElementException и IllegalStateException.
4. Как прочитать Scanner-ом число в шестнадцатеричной или двоичной системе счисления?
Как прочитать Scanner-ом число в шестнадцатеричной или двоичной системе счисления?
Передайте основание системы счисления (radix) в числовой метод: nextInt(16), nextLong(2), hasNextInt(8). Чтобы не указывать его в каждом вызове, задайте основание для всего Scanner методом useRadix(); по умолчанию radix равен 10.
try (Scanner scanner = new Scanner("ff 1010 777 0xff")) {
System.out.println(scanner.nextInt(16)); // 255
System.out.println(scanner.nextInt(2)); // 10
scanner.useRadix(8);
System.out.println(scanner.nextInt()); // 511
System.out.println(scanner.hasNextInt(16)); // false
}
Префикс 0x Scanner не понимает: токен "0xff" не проходит даже hasNextInt(16). Метод reset() возвращает radix 10, локаль и разделитель по умолчанию, но позицию чтения не меняет.
5. Чем Scanner отличается от BufferedReader и Console и когда что выбирать?
Чем Scanner отличается от BufferedReader и Console и когда что выбирать?
Scanner (java.util) сразу разбирает текст на числа и слова (nextInt(), nextDouble(), регулярные выражения), но медленнее на больших файлах, так как проверяет каждый токен регулярным выражением; буфер 1024 символа, не потокобезопасен, ошибки ввода-вывода проглатывает (доступны через ioException()). Подходит для небольших файлов и строк с числами, конфигов, учебных задач.
BufferedReader (java.io) быстро читает строки через readLine() с буфером 8192 символа, но ничего не парсит: дальше нужны Integer.parseInt() или split(). Методы синхронизированы, ошибки приходят проверяемым IOException. Выбор для логов, больших файлов и задач на скорость.
Console (System.console()) предназначен для интерактивного терминала и умеет читать пароль без отображения (readPassword()). Ошибки бросает как IOError. Если терминала нет, например при перенаправлении ввода из файла, System.console() возвращает null.