Приложение Java: невозможно правильно прочитать закодированный файл iso-8859-1

У меня есть файл, который закодирован как ISO-8859-1 и содержит такие символы, как ô .

Я читаю этот файл с кодом Java, что-то вроде:

File in = new File("myfile.csv");
InputStream fr = new FileInputStream(in);
byte[] buffer = new byte[4096];
while (true) {
    int byteCount = fr.read(buffer, 0, buffer.length);
    if (byteCount <= 0) {
        break;
    }

    String s = new String(buffer, 0, byteCount,"ISO-8859-1");
    System.out.println(s);
}

Однако символ ô всегда искажен, обычно печатается как?,

Я прочитал вокруг предмета (и узнал немного по дороге), например,

но до сих пор не могу заставить это работать

Интересно, что это работает на моем локальном компьютере (xp), но не на моей Linux-коробке.

Я проверил, что мой jdk поддерживает требуемые кодировки (они стандартные, так что это не удивительно), используя:

System.out.println(java.nio.charset.Charset.availableCharsets());

5 ответов

Решение

Я подозреваю, что либо ваш файл на самом деле не закодирован как ISO-8859-1, либо System.out не знает, как напечатать символ.

Я рекомендую, чтобы проверить первый, вы изучите соответствующий байт в файле. Чтобы проверить второй, изучите соответствующий символ в строке, распечатав его с

 System.out.println((int) s.getCharAt(index));

В обоих случаях результат должен быть 244 десятичных; 0xf4 гекс.

См. Мою статью об отладке Unicode для общего совета (представленный код написан на C#, но его легко конвертировать в Java, и принципы те же).

В общем, кстати, я бы обернул поток InputStreamReader с правильной кодировкой - это проще, чем создавать новые строки "вручную". Я понимаю, что это может быть просто демо-код, хотя.

РЕДАКТИРОВАТЬ: Вот действительно простой способ доказать, будет ли работать консоль:

 System.out.println("Here's the character: \u00f4");

Разбирать файл как блоки байтов фиксированного размера нехорошо - что если какой-то символ имеет байтовое представление, которое располагается между двумя блоками? Используйте InputStreamReader с соответствующей кодировкой символов:

 BufferedReader br = new BufferedReader(
         new InputStreamReader(
         new FileInputStream("myfile.csv"), "ISO-8859-1");

 char[] buffer = new char[4096]; // character (not byte) buffer 

 while (true)
 {
      int charCount = br.read(buffer, 0, buffer.length);

      if (charCount == -1) break; // reached end-of-stream 

      String s = String.valueOf(buffer, 0, charCount);
      // alternatively, we can append to a StringBuilder

      System.out.println(s);
 }

Кстати, не забудьте проверить, что символ Юникода действительно может отображаться правильно. Вы также можете перенаправить вывод программы в файл, а затем сравнить его с исходным файлом.

Как предполагает Джон Скит, проблема также может быть связана с консолью. Пытаться System.console().printf(s) чтобы увидеть, есть ли разница.

@Joel - ваш собственный ответ подтверждает, что проблема заключается в разнице между кодировкой по умолчанию в вашей операционной системе (UTF-8, которую выбрал Java) и кодировкой, используемой вашим терминалом (ISO-8859-1).

Рассмотрим этот код:

public static void main(String[] args) throws IOException {
    byte[] data = { (byte) 0xF4 };
    String decoded = new String(data, "ISO-8859-1");
    if (!"\u00f4".equals(decoded)) {
        throw new IllegalStateException();
    }

    // write default charset
    System.out.println(Charset.defaultCharset());

    // dump bytes to stdout
    System.out.write(data);

    // will encode to default charset when converting to bytes
    System.out.println(decoded);
}

По умолчанию мой терминал Ubuntu (8.04) использует кодировку UTF-8. С этой кодировкой это напечатано:

UTF-8,

Если я переключаю кодировку терминала на ISO 8859-1, это печатается:

UTF-8,
ôÃ'

В обоих случаях те же байты испускаются программой Java:

5554 462d 380a f4c3 b40a

Разница лишь в том, как терминал интерпретирует полученные байты. В ISO 8859-1, ô кодируется как 0xF4. В UTF-8 ô кодируется как 0xC3B4. Другие символы являются общими для обеих кодировок.

Если вы можете, попробуйте запустить вашу программу в отладчике, чтобы увидеть, что находится внутри вашей строки после ее создания. Возможно, он имеет правильное содержимое, но вывод искажается после вызова System.out.println (s). В этом случае, вероятно, существует несоответствие между тем, что Java считает кодировкой вашего вывода, и кодировкой символов вашего терминала / консоли в Linux.

По сути, если он работает на вашем локальном ПК с XP, но не в Linux, и вы анализируете один и тот же файл (т. Е. Вы передали его в двоичном виде между коробками), то он, вероятно, как-то связан с System.out. распечатать звонок. Я не знаю, как вы проверяете вывод, но если вы делаете это, подключаясь к удаленной оболочке из коробки XP, то нужно рассмотреть набор символов оболочки (и клиента).

Кроме того, то, что предлагает Зак Скривена, также верно: вы не можете предполагать, что вы можете создавать строки из кусков данных таким образом - либо использовать InputStreamReader, либо сначала считывать полные данные в массив (очевидно, не будет работать для большого файла), Однако, поскольку он работает на XP, я бы рискнул предположить, что в данном конкретном случае это, вероятно, не ваша проблема.

Другие вопросы по тегам