Класс Scanner в Java: чтение файлов, строк и разбор токенов - Вопросы

Всего: 5 вопросов

1. 

Как прочитать текстовый файл построчно с помощью 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?

Оба метода появились в 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, а запоминают его: при ошибке ввода-вывода 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-ом число в шестнадцатеричной или двоичной системе счисления?

Передайте основание системы счисления (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 (java.util) сразу разбирает текст на числа и слова (nextInt(), nextDouble(), регулярные выражения), но медленнее на больших файлах, так как проверяет каждый токен регулярным выражением; буфер 1024 символа, не потокобезопасен, ошибки ввода-вывода проглатывает (доступны через ioException()). Подходит для небольших файлов и строк с числами, конфигов, учебных задач.

BufferedReader (java.io) быстро читает строки через readLine() с буфером 8192 символа, но ничего не парсит: дальше нужны Integer.parseInt() или split(). Методы синхронизированы, ошибки приходят проверяемым IOException. Выбор для логов, больших файлов и задач на скорость.

Console (System.console()) предназначен для интерактивного терминала и умеет читать пароль без отображения (readPassword()). Ошибки бросает как IOError. Если терминала нет, например при перенаправлении ввода из файла, System.console() возвращает null.

Страница 1 из 1