BinaryWriter пишет забавных персонажей

Ниже приведен код:

using (FileStream fs = File.Create("data.txt"))
using (BinaryWriter bw = new BinaryWriter(fs))
{
   int num = 2019;
   bw.Write(num);
}

когда я открываю data.txt в своем редакторе, я вижу только забавный персонаж. поэтому мои вопросы:

В1. Это связано с кодировкой моего редактора UTF-8, которая несовместима с форматом BinaryWriter? какую схему кодирования мне следует использовать, чтобы увидеть акт 2019 года в текстовом файле?

Q2 — каково практическое использование BinaryWriter по сравнению с другими потоковыми адаптерами, такими как StreamWriter? для меня BinaryWriter делает некоторые странные вещи, например, вы используете BinaryWriter, чтобы сначала написать int, затем написать строку..., затем, когда вы читаете файл с помощью BinaryReader, вы должны сделать ReadInt32(), а затем ReadString( ), вы не можете испортить последовательность, если вы сделаете ReadString(), вы получите забавный символ. но кто будет «запоминать» или знать последовательности, которые нужно читать?

В1. Вы создали не текстовый файл, а двоичный файл с расширением .txt. Q2 - Вы когда-нибудь использовали файл MP3? Как насчет открытия файла изображения? Вы когда-нибудь запускали исполняемый файл (EXE)? А как насчет видеофайла? Все они имеют очень строгие форматы. Возьмем, к примеру, формат wav-файла.

DiplomacyNotWar 10.07.2019 08:58

Если вы хотите написать текстовый файл, используйте не BinaryWriter, а StreamWriter, производный от TextWriter.

ckuri 10.07.2019 09:02

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

DiplomacyNotWar 10.07.2019 09:13

@John, поэтому я создал двоичный файл с расширением .txt. Но чего я не понимаю, так это того, что вставленное целое отображается как забавный символ, вставленная строка действительно читаема, так почему же строка читается, а не целое?

user11224591 10.07.2019 09:30

Рекомендую посмотреть ваш файл в шестнадцатеричном редакторе. Это должно прояснить ситуацию. По сути, int хранится как 32-битный двоичный код (т.е. 4 байта), а не как 1 байт на цифру. Я напишу ответ позже, если у меня будет время, и если Бруно не хватит.

DiplomacyNotWar 10.07.2019 09:35

Я предполагаю, что BinaryWriter записывает строки в кодировке UTF-8, которая, даже если ваш зритель не знает кодировку, удобочитаема для основных символов. Числа записываются не как текст, а с их внутренним двоичным представлением.

ckuri 10.07.2019 19:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
161
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Все дело в формате файла.

Когда вы используете StreamWriter, ваш вывод будет в читаемом тексте, что означает, что вы можете увидеть, что находится внутри в редакторе. Например, вы можете написать bool "true" или "false" При использовании двоичного записывающего устройства значение сохраняется в двоичном представлении, которое будет равно 0 или 1 для логического значения. Обратите внимание, что вы можете в текстовом файле написать "0" вместо true, если хотите.

Когда дело доходит до запоминания того, что находится внутри, либо вы используете формат файла, который сам описывает себя, например, csv с заголовками, либо вы должны использовать стандартный формат (например, MP3, для которого вы можете найти описание в Интернете), либо вы должны напишите и для чтения, и для записи одновременно, чтобы убедиться, что они совпадают (даже в текстовом формате).

Например, глядя на "0,0", вы не можете сказать, разделены ли два логических значения запятой или число 0 во французском формате с точностью до одной цифры.

Ответ принят как подходящий

Хорошо, давайте начнем с того, что делает ваш код (см. мои добавленные комментарии):

// create a FileStream to data.txt (a file with a .txt extension - not necessarily a text file) 
using (FileStream fs = File.Create("data.txt"))

// wrap the stream in the BinaryWriter class, which assists in writing binary files
using (BinaryWriter bw = new BinaryWriter(fs))
{
   // create a 32-bit integer
   int num = 2019;
   // write a 32-bit integer as 4 bytes
   bw.Write(num);
}

Первое, что вы заметите, это то, что вы пишете не текстовый файл, а двоичный файл. Расширения файлов являются условностью и, возможно, говорят нам, что мы должны ожидать найти в файле, но они не являются истиной евангелия. Я мог бы взять копию Chrome.exe и переименовать ее в Chrome.txt, но это не делает ее текстовым файлом.

Which encoding scheme should I use to be able to see the act 2019 in the text file?

Когда мы говорим о кодировании, таком как UTF-8, мы говорим о кодировании текста — как преобразовать текст в байты, но мы не имеем дело с текстом в вашем коде, поэтому не существует применимого формата кодирования текста для просмотр бинарного файла.

What's the practical uses of BinaryWriter over other stream adapter such as StreamWriter?

Он позволяет быстро создавать двоичный формат из значений в .NET. Например, вместо того, чтобы вручную преобразовывать значение int в 4 байта, вы можете вызвать bw.Write(num); и аналогичным образом прочитать эти данные, например, с помощью BinaryReader и br.ReadInt32().

You can't mess up the sequence, if you do ReadString(), you get a funny character. but who will 'remember' or know the sequences to read?

Когда мы говорим о «форматах файлов», мы обычно имеем в виду соглашения, которым мы следуем при чтении файла. Причина, по которой мы можем запустить приложение, прочитать ZIP-файл, прослушать MP3-файл или просмотреть растровое изображение, заключается в том, что используемое нами программное обеспечение было написано для понимания этих двоичных форматов.

Если взять в качестве примера растровое изображение, существует множество документов, описывающих формат файла. Быстрый поиск в Google показывает Вот этот, Вот этот и Вот этот. Вы можете взять любой из них и создать программу для записи файла изображения с помощью BinaryWriter.

Теперь, если бы вы создавали свой собственный формат, вы, вероятно, написали бы средство записи и чтения одновременно или, по крайней мере, посмотрели бы код для средства записи, когда дело доходит до написания средства чтения (если только у вас нет спецификации, которой нужно следовать, в в каком случае вы могли бы использовать это).

But what I don't get is, the int I inserted is displayed as a funny character, the string I inserted is actually readable, so why string is readable but not int?

Когда вы вызываете Write(string), вы на самом деле записываете две вещи: информацию о длине строки, а затем записываете саму строку. Для этого BinaryWriter должен преобразовать строку в байты, что он делает за вас за кулисами. Вы можете прочитать об этом здесь и в документах.

Итак, почему вы можете прочитать строку в вашем файле? Ну, это потому, что используемая здесь кодировка текста — это та же кодировка, которую вы могли бы использовать для записи текстового файла. Ваш текстовый редактор сделает все возможное, чтобы отобразить содержимое всего файла. Вы можете увидеть это, если перетащите любой двоичный файл (например, Chrome.exe) в текстовый редактор.

   

Итак, как вы просматриваете содержимое вашего файла? Ну, вы можете использовать шестнадцатеричный редактор. Шестнадцатеричный редактор позволяет просматривать и редактировать двоичные файлы. Шестнадцатеричный редактор обычно показывает ваш файл как шестнадцатеричный с одной стороны и пытается отобразить его как текст с другой.

Итак, представьте, что ваш код таков:

using (FileStream fs = File.Create("data.txt"))
using (BinaryWriter bw = new BinaryWriter(fs))
{
   int num = 2019;
   bw.Write(num);
   bw.Write("hello");
}

Если мы откроем его в шестнадцатеричном редакторе, то увидим следующее. Обратите внимание, что пробелы между шестнадцатеричными значениями предназначены только для облегчения чтения и не являются представлением чего-либо в файле:

E3 07 00 00 05 68 65 6C 6C 6F

Здесь есть три части:

E3 07 00 00    - the hexadecimal expression of little endian 2019
05             - indicating that the string is 5 _bytes_ long
68 65 6C 6C 6F - the hexadecimal representations of each character of the string "hello"

Вы можете прочитать о порядке байтов здесь. Думайте об этом как о том, пишет ли компьютер числа «слева направо» или «справа налево».

Таким образом, глядя на значение int, сохраненное выше, мы могли бы записать его в двоичном формате с обратным порядком байтов (1 справа) как:

<  00   >  <  00   >  <  07   >  <  E3   >
0000 0000  0000 0000  0000 0111  1110 0011

Затем мы можем рассчитать это обратно до 2019 года, вашего исходного значения.

Обратите внимание, что информация о длине строки может быть больше одного байта (в соответствии с этот ответ).

Файлы представляют собой цепочки чисел, например 13, 59, 93. Чтобы понять содержимое файла, вам нужен формат — по сути, описание того, что означает содержимое. Чтобы просмотреть байты файла, вы можете использовать шестнадцатеричный редактор (вместо текстового редактора).

Одним из таких форматов является текстовый файл. Имейте в виду, что нет формата текстового файла один — как вы уже заметили, ваш текстовый редактор позволяет вам выбрать кодировку, которую он будет использовать при интерпретации текстового файла. Если вы выберете неправильную кодировку, текст будет другим (хотя вы можете этого не заметить в большинстве кодировок на английском языке, поскольку многие символы идентичны в большинстве современных кодировок). Кодирование — это то, что переводит число 65 (фактически хранящееся в файле) в символ 'A'. Помимо кодирования есть много других сложностей, которые я оставлю на потом.

Вы используете BinaryWriter. Как следует из названия, он предназначен для записи файлов бинарный, а не текстовых файлов. Если вы хотите писать простые текстовые файлы, используйте вместо этого StreamWriter. Двоичный файл обычно более компактен, чем текстовый файл, и предназначен для использования конкретными приложениями, а не для непосредственного чтения или изменения пользователями. Вы по-прежнему можете писать текст внутри бинарного файла — это именно то, что делает bw.Write("Hello"); и поскольку он использует ту же кодировку (по умолчанию), что и ваш текстовый редактор, вы фактически видите слово «Hello» в своем редакторе. Имейте в виду, что есть также «забавные символы» до «Привет» — но для такой короткой строки они не видимый (некоторые могут отображаться как пробел, другие как управляющие символы, такие как «конец строки» или «табуляция»; вы даже можете написать звуковой сигнал, который будет выполнен, если вы распечатаете файл). Они представляют длину следующей строки, что позволяет быстро прочитать строку и только строку (или пропустить ее при чтении файла).

Теперь для чтения и записи файлов требуется определенная симметрия. Как вы заметили, если вы пишете файл как «сначала число, затем строка», вам также необходимо читать как «сначала число, затем строка». Не имеет значения, является ли файл текстовым или двоичным - например, вы хотите записать GPS-координаты в файл. Если вы напишете сначала широту, а затем долготу, другая программа (или пользователь), читающая файл сначала как долготу, получит неверный результат. Простой формат файла, подобный этому, зависит от порядка и совершенно нетерпим к любым ошибкам - пропускайте одну строку при чтении или записи, и все становится совершенно нечитаемым.

Но, конечно, это не единственный способ разработки формата файла (хотя он, безусловно, очень распространен). Существуют форматы, специально разработанные, чтобы быть менее строгими. Например, вместо набора строк или значений, разделенных запятыми, вы можете сохранить свои данные в файле JSON:

{
  "longitude": 12.365,
  "lattitude": 32.131
}

Основное преимущество заключается в том, что формат более информативен и удобен для чтения (и записи); сразу видно, что широта 32.131. Приложению все еще нужно понимать, что такое «широта», но вы можете видеть, что здесь определенно есть прогресс. Это также более терпимо к некоторым видам изменений — например, приложению чтения не нужно заботиться о том, отсутствуют ли некоторые поля (и отображается неполная информация, а не полный беспорядок) или добавлены новые поля. Он не заботится о порядке полей.

Это дорого обходится. Файл на много больше (простой двоичный файл может быть 8 байт или меньше по сравнению с примерно 40 байтами для образца JSON; это становится еще более заметным, если задействованы массивы и т. д.). Программе намного сложнее анализировать, что может замедлить загрузку файла. Отсутствие строгости в отношении формата также имеет свои преимущества и недостатки — может быть очень сложно гарантировать, что программа правильно обрабатывает все потенциальные входные данные, особенно если имеется несколько разных читателей и записывающих устройств.

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

В конце концов, вам нужно сделать выбор в отношении формата, который вы хотите использовать для сохранения материала. Каждый из них имеет свой набор преимуществ и недостатков. Некоторые из них очень просты, например, просто используйте BinaryWriter, чтобы написать известную последовательность. Некоторые поддерживают совместимость версий, поэтому новое приложение может читать или записывать файлы старого приложения или наоборот. Некоторые из них специально оптимизированы для определенных целей, таких как быстрый поиск по содержимому файла или эффективное хранение изображений. Некоторые из них предназначены в основном для простоты использования (например, JSON и Protobuf или .NET BinarySerializer).

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

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