Понимание двоичного, ByteStream и символов в Java

У меня есть некоторые трудности с перевариванием некоторых концепций в классах Java IO. Например, есть два типа потоков: byte и char. Потоки байтов как я понимаю читают побайтно.

1. Если char в java хранится как 16-битный (двухбайтовый) тип данных, как я могу точно прочитать char, скажем, «A», из файла, используя входной поток, ориентированный на байты, например. FileInputStream?

2. Дело в том, что символы, которые я использовал (в основном между 0 и 122 на диаграмме ascii), хранятся в одном байте из двух выделенных байтов?

3. DataInputStream/DataOutputStream позволяет мне читать и записывать двоичные данные, другие входные потоки, такие как FileInputStream/FileOutputStream, позволяют мне читать и записывать что именно? Я в основном хочу знать, какой поток использовать, когда я хочу выводить данные в виде текста, который я могу прочитать (с помощью простого текстового редактора, такого как блокнот), а когда я хочу, чтобы он был закодирован как необработанные двоичные данные (текст, который выглядит как мусор в блокноте)?

Пытаюсь понять концепцию потоков в java и что и когда использовать.


person jmreader    schedule 02.01.2014    source источник
comment
Потоки символов имеют дело с символами, а не с байтами. Утверждение, что потоки символов считывают байт за байтом, неверно.   -  person davmac    schedule 02.01.2014
comment
char является 16-битным типом данных. Он не хранит символ; Он хранит кодовую единицу UTF-16. Ровно одна или две кодовые единицы UTF-16 составляют кодовую точку UTF-16. Кодовая точка идентифицирует конкретный символ Unicode. Кроме того, вы смотрите на неправильный набор символов. Java обычно использует Unicode, хотя некоторые потоковые классы по умолчанию используют набор символов ОС по умолчанию.   -  person Tom Blodget    schedule 02.01.2014


Ответы (3)


Зависит от формата файла, который вы читаете.

Если файл представляет собой поток байтов ASCII, сделайте следующее:

InputStream is = new FileInputStream( filePath );
Reader reader = new InputStreamReader( is, "ISO-8859-1" );

char ch = reader.read();

Вы всегда сначала открываете входной поток в файле, ориентированном на байты. Затем InputStreamReader преобразует байты в символы. Конечно, в этом случае ISO-8859-1 представляет собой сопоставление однобайтовых значений с точно такими же символьными значениями. Очевидно, что возможно и другое сопоставление, но ISO-8859-1 совпадает с первыми 255 символами набора Unicode, а первые 127 из них совпадают с ASCII.

При написании используйте:

OutputStream os = new FileOutputStream( filePath ) ;
Writer w = new OutputStreamWriter( os, "ISO-8859-1" );

w.write( ch );

Еще раз, это OutputStreamWriter, который соответствующим образом преобразует символы и поток байтов в соответствии с набором символов ISO-8859-1. Результирующий файл будет иметь один байт на символ.

Вот еще несколько примеров правильных базовых шаблонов потока.

Если вы используете вышеизложенное, вы выполняете это:

w.write("AAAA");
w.flush();
w.close();

Результирующий файл будет содержать 4 байта со значением 65 в каждом байте. Повторное чтение этого файла с использованием кода вверху приведет к четырем символам «A» в памяти, но в памяти они занимают 16 бит для каждого символа.

Если файл закодирован в другом наборе символов, включая, возможно, многобайтовые символы, просто используйте правильную кодировку в InputStreamReader/OutputStreamWriter, и правильное преобразование будет происходить при чтении и записи.

UTF-8 — это не набор символов, а скорее кодировка обычных символов Юникода в последовательности байтов, и оказывается, что кодировка UTF-8 довольно умна, поскольку первые 127 символов символов Юникода отображаются в первые 127 символов. байтовые значения (как отдельные байты сами по себе). Затем символы> = 128 используют 2 или более байтовых значения в строке, где каждое из этих байтовых значений> = 128. Если вы знаете, что файл ascii использует только «7-битный» ASCII, тогда UTF-8 будет работать для вас также. Для Java в целом UTF-8 является лучшей кодировкой для использования в файле, поскольку она может правильно кодировать все возможные значения символов Java без потерь.

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

(Бывает еще хуже... на самом деле Символ - это 32-битная величина, из которых 20 бит могут быть закодированы в последовательности 16-битных значений char с кодировкой, называемой UTF-16. Рекомендуем пока игнорировать это, но просто будьте известно, что даже в строке Java, которая состоит из 16-битных значений символов, есть некоторые последовательности с двойными символами.)

person AgilePro    schedule 02.01.2014
comment
Хороший ответ, но... Java Charset для ASCII используется US-ASCII, а не ISO-8859-1. Я полагаю, вы упомянули ISO-8859-1, потому что трудно найти пример, где все еще используется ASCII. - person Tom Blodget; 02.01.2014
comment
Я понимаю, что вы сказали. Иногда это может немного сбивать с толку из-за большого количества опций для чтения файла. Некоторые вещи я не совсем понимаю, но я хочу прочитать еще кое-что, основанное на вашем ответе и других ответах, повозиться с кодом и посмотреть, что у меня получится. Большое спасибо! - person jmreader; 03.01.2014
comment
@Tom Технически ты прав, но этот ответ не так полезен. Вместо этого всегда следует использовать ISO-8859-1. US-ASCII определяет только 128 символов. Байт может содержать 256 значений. Что делать с этими другими ценностями? Первые 128 символов ISO-8859-1 точно такие же, как US-ASCII. Если файл содержит только 7-битный ASCII, то обе кодировки будут работать одинаково хорошо; нет преимущества для US-ASCII. Но если появляются значения байтов › 127, то ISO-8859-1 обеспечивает разумную обработку их, и ISO-8859-1 является кодировкой по умолчанию в WWW, поэтому многие файлы кодируются таким образом. - person AgilePro; 08.01.2014

Если char в java хранится как 16-битный (двухбайтовый) тип данных, как я могу точно прочитать char, скажем, «A», из файла, используя входной поток, ориентированный на байты, например. FileInputStream?

Попробуйте сделать

System.out.println(Integer.toBinaryString('A'));

который выводит двоичное представление символа 'A'. Это печатает

1000001

Поскольку 'A' — это char, на самом деле он хранится в 16 битах.

00000000 01000001

Так что все, что вам нужно сделать, это прочитать два последовательных байта и использовать их соответствующим образом, чтобы сформировать char. Увидеть это в действии

ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0b00000000, 0b01000001});
System.out.println(buffer.getChar());

который печатает

A

Это берет первые byte в массиве и использует их как первые 8 бит в char, а вторые byte — как последние 8 бит.


DataInputStream/DataOutputStream позволяет мне читать и записывать двоичные данные, другие входные потоки, такие как FileInputStream/FileOutputStream, позволяют мне читать и записывать что именно? Я в основном хочу знать, какой поток использовать, когда я хочу выводить данные в виде текста, который я могу прочитать (с помощью простого текстового редактора, такого как блокнот), а когда я хочу, чтобы он был закодирован как необработанные двоичные данные (текст, который выглядит как мусор в блокноте)?

Пишете ли вы текст или что-то еще, это все биты и байты. Вы можете очень хорошо сделать

"someString".getBytes()

и напишите тех. Так что это не имеет большого значения. Используйте то, что наиболее характерно для того, что вы делаете. Как правило, базовый OutputStream можно обернуть PrintWriter, а базовый InputStreamScanner или BufferedReader.

person Sotirios Delimanolis    schedule 02.01.2014
comment
Мне нравится ваш ответ, это здорово - ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0b00000000, 0b01000001}); System.out.println(buffer.getChar()); - person Farhan Shirgill Ansari; 10.11.2014

Прежде чем я попытаюсь ответить на ваш вопрос, нужно понять несколько очень простых вещей.

  1. На самом низком уровне InputStream/OutputStream) все биты и байты. Таким образом, потоки самого низкого уровня имеют дело с необработанными данными, которые представляют собой биты/байты.
  2. Теперь, чтобы преобразовать необработанные байты в читаемые символы, вам потребуется кодировка символов или набор символов. . Короче говоря, кодировка символов — это инструкция (преобразование байтов в визуальные символы) для перевода необработанных байтов в читаемые символы из определенного набора (например, UTF-8).

Теперь перейдем к вашим вопросам:

Если char в java хранится как 16-битный (двухбайтовый) тип данных, как я могу точно прочитать char, скажем, «A», из файла, используя входной поток, ориентированный на байты, например. FileInputStream?

Для чтения символьных данных необработанные входные потоки завернуты в символьно-ориентированные потоки, например

FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF8"); 

Как сказано в javadoc InputStreamReader является мостом между потоками байтов и потоками символов .

Может быть, используемые мной символы (в основном от 0 до 122 на диаграмме ascii) хранятся в одном байте из двух выделенных байтов?

да. Набор символов ascii является подмножеством более крупного набора Unicode, такого как UTF-8.

DataInputStream/DataOutputStream позволяет мне читать и записывать двоичные данные, другие входные потоки, такие как FileInputStream/FileOutputStream, позволяют мне читать и записывать что именно?

Думаю, теперь очевидно, что DataInputStream/DataOutputStream для символьных данных, а ileInputStream/FileOutputStream для необработанных данных.

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

Для текста используйте любые программы чтения/записи (вот пример )

person Santosh    schedule 02.01.2014