Чтение файлов из Windows и Linux дает разные результаты (кодировка символов?) - PullRequest
6 голосов
/ 16 июня 2011

В настоящее время я пытаюсь прочитать файл в формате MIME, который содержит некоторые двоичные строковые данные в формате PNG.

В Windows чтение файла дает мне правильную двоичную строку, то есть я просто копирую строку и меняю расширение на png и вижу картинку.


Пример после чтения файла в Windows приведен ниже:

    --fh-mms-multipart-next-part-1308191573195-0-53229
     Content-Type: image/png;name=app_icon.png
     Content-ID: "<app_icon>"
     content-location: app_icon.png

    ‰PNG

и т.д ... и т.д ...

Пример после чтения файла в Linux приведен ниже:

    --fh-mms-multipart-next-part-1308191573195-0-53229
     Content-Type: image/png;name=app_icon.png
     Content-ID: "<app_icon>"
     content-location: app_icon.png

     �PNG

и т.д ... и т.д ...


Я не могу преобразовать версию для Linux в рисунок, так как все это превращается в несколько забавных символов с большим количеством перевернутых слов "?" и символы "1/2".

Может кто-нибудь объяснить мне, что происходит, и, возможно, предложить решение? Играю с кодом уже неделю и больше.

Ответы [ 2 ]

20 голосов
/ 16 июня 2011

� представляет собой последовательность из трех символов - 0xEF 0xBF 0xBD и является представлением UTF-8 кодовой точки Unicode 0xFFFD. Самой кодовой точкой является символ замены для недопустимых последовательностей UTF-8.

Очевидно, по какой-то причине набор подпрограмм, задействованных в вашем исходном коде (в Linux), обрабатывает заголовок PNG неточно. Заголовок PNG начинается с байта 0x89 (за которым следует 0x50, 0x4E, 0x47), что правильно обрабатывается в Windows (что может относиться к файлу как к последовательности из CP1252 байтов). В CP1252 символ 0x89 отображается как .

Однако в Linux этот байт декодируется подпрограммой UTF-8 (или библиотекой, которая считала, что было бы хорошо обработать файл как последовательность UTF-8). Поскольку само по себе 0x89 не является допустимой кодовой точкой в ​​диапазоне ASCII-7 (ref: схема кодирования UTF-8 ), оно не может быть сопоставлено с действительной кодовой точкой UTF-8 в 0x00-0x7F. спектр. Кроме того, он не может быть сопоставлен с действительной кодовой точкой, представленной в виде многобайтовой последовательности UTF-8, поскольку все многобайтовые последовательности начинаются с минимального набора из 2 битов, равного 1 (11....), и поскольку это начало файла, он также не может быть байтом продолжения. В результате получается, что декодер UTF-8 теперь заменяет 0x89 символами замены UTF-8 0xEF 0xBF 0xBD (как глупо, учитывая, что файл не является UTF-8 для начала), который будет отображаться в ISO-8859-1 как �.

Если вам нужно решить эту проблему, вам необходимо обеспечить следующее в Linux:

  • Считать байты в файле PNG, используя подходящую кодировку для файла (то есть не UTF-8); Это, очевидно, необходимо, если вы читаете файл как последовательность символов *, и не требуется, если вы читаете только байты. Возможно, вы делаете это правильно, поэтому было бы целесообразно также проверить последующие шаги.
  • При просмотре содержимого файла используйте подходящий редактор / просмотр, который не выполняет никакого внутреннего декодирования файла в последовательность байтов UTF-8. Использование подходящего шрифта также поможет, поскольку вы можете захотеть предотвратить беспрецедентный сценарий, когда глиф (для 0xFFFD это на самом деле символ ромба ) не может быть представлен, и может привести к дальнейшим изменениям (маловероятно, но вы никогда не узнаете как был написан редактор / зритель).
  • Также неплохо записать файлы (если вы делаете это) в подходящей кодировке - возможно, ISO-8859-1 вместо UTF-8. Если вы обрабатываете и храните содержимое файла в памяти в виде байтов, а не символов, тогда достаточно записи их в выходной поток (без участия каких-либо строк или ссылок на символы).

* Очевидно, Java Runtime выполнит декодирование последовательности байтов в кодовые точки UTF-16, если вы преобразуете последовательность байтов в символ или объект String.

7 голосов
/ 17 июня 2011

В Java Stringbyte[].

  • byte[] представляет необработанные двоичные данные.
  • String представляет текст, с которым связана кодировка / кодировка, позволяющая определить, какие символы он представляет.

Двоичные данные ≠ Текст .

Текстовые данные внутри String имеют Unicode / UTF-16 в качестве кодировки / кодировки (или Unicode / mUTF-8 при сериализации). Всякий раз, когда вы конвертируете что-то, что не является String в String или наоборот, вам нужно указать кодировку / кодировку для текстовых данных, отличных от String (даже если вы делаете это неявно, используя кодировка платформы по умолчанию ).

Файл PNG содержит необработанные двоичные данные, представляющие изображение (и соответствующие метаданные), , а не текст. Поэтому не стоит воспринимать его как текст.

\x89PNG это не текст, это просто «волшебный» заголовок для идентификации файлов PNG. 0x89 это даже не символ, это просто произвольное значение байта, и его единственные разумные представления для display такие вещи, как \x89, 0x89, ... Аналогично, PNG там на самом деле это двоичные данные, это могло бы быть 0xdeadbeef и ничего бы не изменилось. Тот факт, что PNG оказывается читабельным для человека, является просто удобством.

Ваша проблема связана с тем, что ваш протокол смешивает текстовые и двоичные данные, тогда как Java (в отличие от некоторых других языков, таких как C) обрабатывает двоичные данные иначе, чем текстовые.

Java предоставляет *InputStream для чтения двоичных данных и *Reader для чтения текста. Я вижу два способа справиться с вводом:

  • Обрабатывать все как двоичные данные. Когда вы читаете целую текстовую строку, преобразуйте ее в String, используя соответствующую кодировку / кодировку.
  • Слой InputStreamReader поверх InputStream, прямой доступ к InputStream, когда вы хотите двоичные данные, доступ к InputStreamReader, когда вы хотите текст.

Возможно, вы захотите буферизацию, правильное место для размещения во втором регистре ниже *Reader. Если вы используете BufferedReader, BufferedReader, вероятно, будет потреблять больше ввода от InputStream, чем следовало бы. Итак, у вас будет что-то вроде:

 ┌───────────────────┐
 │ InputStreamReader │
 └───────────────────┘
          ↓
┌─────────────────────┐
│ BufferedInputStream │
└─────────────────────┘
          ↓
   ┌─────────────┐
   │ InputStream │
   └─────────────┘

Вы будете использовать InputStreamReader для чтения текста, затем вы будете использовать BufferedInputStream для чтения соответствующего количества двоичных данных из того же потока.

Проблемным случаем является распознавание "\r" (старая MacOS) и "\r\n" (DOS / Windows) в качестве ограничителей строки. В этом случае вы можете прочитать один символ слишком много. Можно использовать подход, который использовался в устаревшем методе DataInputStream.readline(): прозрачно обернуть внутренний InputStream в PushbackInputStream и прочитать этот символ.

Однако, поскольку у вас, похоже, нет Content-Length , я бы порекомендовал первый способ, рассматривая все как двоичный файл, и преобразовывать в String только после чтения всей строки. В этом случае я бы рассматривал разделитель MIME как двоичные данные.

Выход:

Поскольку вы имеете дело с двоичными данными, вы не можете просто println() их. PrintStream имеет write() методы, которые могут работать с двоичными данными (например, для вывода в двоичный файл).

Или, возможно, ваши данные должны быть переданы по каналу, который обрабатывает их как текст. Base64 предназначен для этой конкретной ситуации (передача двоичных данных в виде текста ASCII). Форма в кодировке Base64 использует только символы US_ASCII, поэтому вы можете использовать ее с любой кодировкой / кодировкой, которая является надмножеством US_ASCII (ISO-8859- *, UTF-8, CP-1252, ...). Поскольку вы конвертируете двоичные данные в / из текста, единственным разумным API для Base64 будет что-то вроде:

String Base64Encode(byte[] data);
byte[] Base64Decode(String encodedData);

, который в основном используется внутренним java.util.prefs.Base64.

Вывод:

В Java Stringbyte[].

Двоичные данные ≠ Текст .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...