【问题标题】:BinaryWriter writes funny charactersBinaryWriter 写出有趣的字符
【发布时间】:2019-11-19 17:49:20
【问题描述】:

下面是代码:

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

当我用我的编辑器打开 data.txt 时,我只看到一个有趣的字符。所以我的问题是:

Q1-这是因为我的编辑器的编码是UTF-8,与BinaryWriter格式不兼容吗?我应该使用哪种编码方案才能在文本文件中看到 2019 年法案?

Q2-BinaryWriter 相对于其他流适配器如 StreamWriter 的实际用途是什么?对我来说 BinaryWriter 做了一些奇怪的事情,例如,你使用 BinaryWriter 先写一个 int,然后写一个字符串……,然后当你用 BinaryReader 读取文件时,你必须做 ReadInt32() 然后 ReadString( ),你不能弄乱序列,如果你做 ReadString(),你会得到一个有趣的角色。但是谁会“记住”或知道要阅读的序列?

【问题讨论】:

  • Q1 - 您尚未创建文本文件,而是创建了扩展名为 .txt 的二进制文件。 Q2 - 你用过 MP3 文件吗?打开一个图像文件怎么办?您是否曾经启动过可执行 (EXE) 文件?视频文件呢?它们都有非常严格的格式。以wav file format 为例。
  • 如果你想写一个文本文件,不要使用 BinaryWriter,而是从 TextWriter 派生的 StreamWriter。
  • 顺便说一句,您可能想使用 hex 编辑器 查看您的文件,以了解幕后发生的事情。
  • @John 所以我创建了一个扩展名为 .txt 的二进制文件。但是我不明白的是,我插入的int显示为一个有趣的字符,我插入的字符串实际上是可读的,那么为什么string是可读的而不是int?
  • 我建议在十六进制编辑器中查看您的文件。它应该使事情更清楚。本质上,int 存储为 32 位二进制(即 4 个字节),而不是每个数字 1 个字节。如果我有时间,我会在稍后写一个答案,如果布鲁诺的不够用。

标签: c# .net io


【解决方案1】:

好的,让我们从您的代码的作用开始(请参阅我添加的 cmets):

// 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,但这并不能使其成为文本文件。

我应该使用哪种编码方案才能在文本文件中看到 2019 年法案?

当我们谈论编码(例如 UTF-8)时,我们谈论的是文本编码 - 如何将文本转换为字节,但我们不处理代码中的文本,因此没有适用的文本用于查看二进制文件的编码格式。

BinaryWriter 相对于其他流适配器(例如 StreamWriter)的实际用途是什么?

它允许您从 .NET 中的值快速创建二进制格式。例如,您可以调用bw.Write(num);,而不必手动将int 值转换为4 个字节,同样您可以使用BinaryReaderbr.ReadInt32() 读取该数据。

你不能弄乱序列,如果你执行 ReadString(),你会得到一个有趣的字符。但是谁会“记住”或知道要阅读的序列?

当我们谈论“文件格式”时,我们通常指的是我们在读取文件时遵循的约定。我们可以启动应用程序、读取 ZIP 文件、收听 MP3 文件或查看位图的原因是因为我们使用的软件是为理解这些二进制格式而编写的。

如果我们以位图为例,描述文件格式的文档有很多。快速谷歌搜索显示this onethis onethis one。您可以使用其中任何一个并创建一个程序来使用BinaryWriter 编写图像文件。

现在,如果您正在创建自己的格式,您可能会同时编写写入器和读取器,或者至少在编写读取器时查看写入器的代码(除非您有规范跟随,在这种情况下你可以使用它)。

但我不明白的是,我插入的int显示为一个有趣的字符,我插入的字符串实际上是可读的,那么为什么string是可读的而不是int?

当你调用Write(string) 时,你实际上是在写两件事:关于字符串长度的信息,然后是写字符串本身。为此,BinaryWriter 必须将字符串转换为字节,这在幕后为您完成。你可以阅读herein the docs

那么为什么你可以读取文件中的字符串呢?嗯,这是因为这里使用的文本编码与您可以用来编写文本文件的编码相同。您的文本编辑器将尽最大努力呈现整个文件的内容。如果您将任何类型的二进制文件(例如Chrome.exe)拖到文本编辑器中,您可以看到这一点。

那么您如何查看文件的内容?好吧,您可以使用hex editor。十六进制编辑器允许您查看和编辑二进制文件。十六进制编辑器通常会在一侧将您的文件显示为十六进制,而在另一侧尝试将其呈现为文本。

所以,想象一下你的代码是这样的:

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"

您可以阅读有关字节顺序的信息here。可以把它想象成计算机是“从左到右”还是“从右到左”书写数字。

所以查看上面存储的 int 值,我们可以将其以 big-endian(右侧为 1)二进制形式编写为:

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

然后我们可以将其计算回 2019 年,即您的原始值。

注意字符串长度信息可以多于一个 bye(按照this answer)。

【讨论】:

    【解决方案2】:

    这都是文件格式的问题。

    当您使用 StreamWriter 时,您的输出将是可读的文本,这意味着您可以在编辑器中看到里面的内容。例如你可以写一个 bool "true""false" 使用二进制写入器时,该值存储在其二进制表示形式中,对于布尔值,该值将是 0 或 1。请注意,如果您愿意,可以在文本文件中将 "0" 写为 true。

    要记住里面的内容,要么使用自我描述的文件格式,例如带有标题的 csv,要么必须使用标准格式(例如 MP3,您可以在网上找到相关说明)或您必须同时编写 reader 和 writer 以确保它们匹配(即使是文本格式)。

    例如,通过查看"0,0",您无法判断它的两个布尔值是用逗号分隔还是法语格式的数字 0 具有一位精度。

    【讨论】:

      【解决方案3】:

      文件是一串数字——比如 13、59、93。要理解文件的内容,您需要一个格式——本质上是对内容含义的说明。要查看文件的字节,您可以使用十六进制编辑器(而不是文本编辑器)。

      文本文件就是这样一种格式。请注意,没有 one 文本文件格式 - 正如您已经注意到的那样,您的文本编辑器允许您选择在解释文本文件时将使用的编码。如果您选择了错误的编码,文本会有所不同(尽管您可能不会注意到大多数英文编码,因为大多数现代编码中的许多字符都是相同的)。编码是将数字65(实际存储在文件中)转换为字符'A'。除了编码之外,还有很多其他的复杂情况,我会留到以后再说。

      您正在使用BinaryWriter。顾名思义,它旨在编写二进制文件,而不是文本文件。如果要编写纯文本文件,请改用StreamWriter。二进制文件通常比文本文件更紧凑,旨在供特定应用程序使用,而不是由用户直接读取或修改。你仍然可以在二进制文件中写入文本——这正是bw.Write("Hello") 所做的;并且由于它使用与文本编辑器相同的编码(默认情况下),因此您实际上会在编辑器中看到“Hello”一词。请注意,“Hello”之前还有“有趣的字符” - 但对于这么短的字符串,它们不可见(有些可能显示为空格,有些则显示为空格)作为“行尾”或“制表符”之类的控制字符;您甚至可以编写一个 beep,如果您打印出文件就会执行)。这些代表后面的字符串的长度,可以让你快速读取字符串,并且只读取字符串(或者在读取文件时跳过它)。

      现在,读写文件需要一定的对称性。如您所见,如果您将文件写为“先数字后字符串”,您还需要将其读取为“先数字后字符串”。文件是文本文件还是二进制文件并不重要——例如,假设您想将 GPS 坐标写入文件。如果您先写纬度,然后再写经度,另一个程序(或用户)首先将文件作为经度读取,将得到错误的结果。像这样的简单文件格式是依赖于顺序的,并且完全不能容忍任何类型的错误 - 读取或写入时跳过一行,整个内容变得完全不可读。

      当然,这不是您设计文件格式的唯一方法(尽管它确实很常见)。有些格式明确设计为不那么严格。例如,您可以将数据保存在 JSON 文件中,而不是一组行或逗号分隔值:

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

      主要好处是格式更具自我描述性和人类可读性(和可写性);一眼就能看出纬度是32.131。应用程序仍然需要了解什么是“纬度”,但您可以看到这里肯定有进步。它对某些类型的更改也更宽容 - 例如,阅读器应用程序不必关心是否缺少某些字段(并显示不完整的信息,而不是完全混乱),或者是否添加了新字段。它不关心字段的顺序。

      这是有代价的。该文件要大得多(一个简单的二进制文件可能是 8 个字节或更少,而示例 JSON 大约为 40 个字节;如果涉及数组等,这会更加明显)。程序解析起来要困难得多,这可能会使加载文件变慢。对格式不严格也有其好处和弊端 - 确保程序正确处理所有潜在输入可能非常困难,尤其是在有多个不同读取器和写入器的情况下。

      二进制也有等效的文件格式,现在最流行的文件格式之一是 Protobuf。它不那么自我描述,也不容易被人类阅读,但它也更严格,更节省空间,读写速度更快。

      最后,您需要选择要用于保存内容的格式。每个都有自己的优点和缺点。有些很简单,比如只用BinaryWriter 写一个众所周知的序列。有些支持版本兼容性,因此较新的应用程序可以读取或写入旧应用程序的文件,反之亦然。有些专门针对某些用途进行了优化,例如在文件内容中启用快速搜索,或有效地存储图像。有些设计主要是为了易于使用(如 JSON 和 Protobuf,或 .NET 的 BinarySerializer)。

      但最后,文件只是一串数字。你需要规则来解释这些数字是有用的。选择适合您需要的规则。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-23
        • 2010-11-09
        • 2012-04-18
        • 1970-01-01
        相关资源
        最近更新 更多