Files.readAllBytes vs Files.lines получает MalformedInputException

Я бы подумал, что следующие два подхода к чтению файла должны вести себя одинаково. Но они этого не делают. Второй подход - это MalformedInputException.

public static void main(String[] args) {    
    try {
        String content = new String(Files.readAllBytes(Paths.get("_template.txt")));
        System.out.println(content);
    } catch (IOException e) {
        e.printStackTrace();
    }

    try(Stream<String> lines = Files.lines(Paths.get("_template.txt"))) {
        lines.forEach(System.out::println);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Это трассировка стека:

Exception in thread "main" java.io.UncheckedIOException: java.nio.charset.MalformedInputException: Input length = 1
    at java.io.BufferedReader$1.hasNext(BufferedReader.java:574)
    at java.util.Iterator.forEachRemaining(Iterator.java:115)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Test.main(Test.java:19)
Caused by: java.nio.charset.MalformedInputException: Input length = 1
    at java.nio.charset.CoderResult.throwException(CoderResult.java:281)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at java.io.BufferedReader$1.hasNext(BufferedReader.java:571)
    ... 4 more

В чем тут разница, и как я могу это исправить?

Ответы

Ответ 1

Это связано с кодировкой символов. Компьютеры занимаются только цифрами. Чтобы сохранить текст, символы в тексте должны быть преобразованы в номера и из них, используя некоторую схему. Эта схема называется кодировкой символов. Существует множество различных кодировок символов; некоторые из известных стандартных кодировок - ASCII, ISO-8859-1 и UTF-8.

В первом примере вы читаете все байты (числа) в файле, а затем преобразуете их в символы, передавая их в конструктор класса String. Это будет использовать кодировку по умолчанию вашей системы (независимо от того, что она находится в вашей операционной системе) для преобразования байтов в символы.

Во втором примере, где вы используете Files.lines(...), кодировка символов UTF-8 будет использоваться, согласно документации. Когда в файле, который не является допустимой последовательностью UTF-8, обнаружена последовательность байтов, вы получите MalformedInputException.

Кодировка символов по умолчанию вашей системы может быть или не быть UTF-8, поэтому это может объяснить разницу в поведении.

Вам нужно будет выяснить, какая кодировка символов используется для файла, а затем явно использовать это. Например:

String content = new String(Files.readAllBytes(Paths.get("_template.txt")),
        StandardCharsets.ISO_8859_1);

Второй пример:

Stream<String> lines = Files.lines(Paths.get("_template.txt"),
        StandardCharsets.ISO_8859_1);

Ответ 2

В дополнение к Jesper ответ, что происходит здесь (и недокументировано!) Files.lines() том, что Files.lines() создает CharsetDecoder чья политика заключается в Files.lines() от неверных последовательностей байтов; то есть его CodingErrorAction устанавливается в REPORT.

Это не похоже на то, что происходит почти для всех других реализаций Reader предоставляемых JDK, стандартная политика которых заключается в REPLACE. Эта политика приведет к тому, что все неиспользуемые последовательности байтов испускают заменяющий символ (U + FFFD).

Ответ 3

Files.lines по умолчанию использует кодировку UTF-8, тогда как создание новой строки String из байтов будет использовать системную кодировку по умолчанию. Похоже, что ваш файл не находится в UTF-8, поэтому он терпит неудачу.

Проверьте, что кодирует ваш файл, и передайте его в качестве второго параметра.

Ответ 4

2017 использование:

 Charset.forName("ISO_8859_1") instead of Charsets.ISO_8859_1