В Java String
≠ byte[]
.
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 String
≠ byte[]
.
Двоичные данные ≠ Текст .