下面是代码:
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格式不兼容?我应该使用哪种编码方案才能在文本文件中看到act 2019?
Q2-与其他流适配器(例如StreamWriter)相比,BinaryWriter的实际用途是什么?对我来说BinaryWriter做一些奇怪的事情,例如,您使用BinaryWriter先写一个int,然后写一个字符串...,然后当您通过BinaryReader读取文件时,必须先执行ReadInt32()然后再执行ReadString( ),则无法弄乱序列,如果您执行ReadString(),则会得到一个有趣的字符。但是谁会“记住”或知道阅读的顺序?
答案 0 :(得分:2)
好的,让我们从您的代码开始做起(请参阅我添加的注释):
// 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
,但这并不能使其成为文本文件。
我应该使用哪种编码方案才能在文本文件中看到act 2019?
当我们谈论编码(例如UTF-8)时,我们谈论的是文本编码-如何将文本转换为字节,但是我们不处理代码中的文本,因此没有适用的文本查看二进制文件的编码格式。
与其他流适配器(例如StreamWriter)相比,BinaryWriter的实际用途是什么?
它使您可以从.NET中的值快速创建二进制格式。例如,不必手动将int
的值转换为4个字节,可以调用bw.Write(num);
,同样可以使用BinaryReader
和br.ReadInt32()
读取该数据,以便例子。
您不能弄乱序列,如果您执行ReadString(),则会得到一个有趣的字符。但是谁会“记住”或知道阅读的顺序?
当我们谈论“文件格式”时,通常是指我们阅读文件时遵循的约定。之所以可以启动应用程序,读取ZIP文件,收听MP3文件或查看位图,是因为我们使用的软件已被编写为可以理解这些二进制格式。
如果以位图为例,则有许多描述文件格式的文档。快速的Google搜索会显示this one,this one和this one。您可以选择其中任何一个,并创建一个程序来使用BinaryWriter
写入图像文件。
现在,如果要创建自己的格式,则可能会同时编写writer和reader,或者至少要在编写Reader时查看一下writer的代码(除非您有规范请遵循,在这种情况下,您可以使用它。
但是我没有得到的是,我插入的int显示为一个有趣的字符,我插入的字符串实际上是可读的,那么为什么字符串可读但不可读?
调用Write(string)
时,实际上是在写两件事:有关字符串长度的信息,然后编写字符串本身。为此,BinaryWriter
必须将字符串转换为字节,这将在后台为您完成。您可以了解有关here和in 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年。
请注意,字符串长度信息可以超过一个再见(根据this answer)。
答案 1 :(得分:1)
这完全取决于文件格式。
使用StreamWriter时,输出将以可读文本显示,这意味着您可以看到编辑器中的内容。例如,您可以编写布尔"true"
或"false"
使用二进制编写器时,该值以其二进制表示形式存储,对于布尔值,该表示形式将为0或1。请注意,如果愿意,您可以在文本文件中将"0"
写为true。
当要记住里面的内容时,可以使用自我描述的文件格式(例如带标题的csv),或者必须使用标准格式(例如可以在线查找描述的MP3)或您必须同时编写读写器,以确保它们匹配(即使使用文本格式也是如此)。
例如,通过查看"0,0"
,您无法确定它的两个布尔值是由逗号分隔还是法语格式的数字0(具有一位精度)。
答案 2 :(得分:1)
文件是一串数字,例如13、59、93。要了解文件的内容,您需要一种 format -本质上是对内容含义的描述。要查看文件的字节,可以使用十六进制编辑器(而不是文本编辑器)。
一种这样的格式是文本文件。请注意,没有 one 文本文件格式-正如您已经注意到的那样,您的文本编辑器允许您选择在解释文本文件时将使用的编码。如果您选择了错误的编码,则文本将有所不同(尽管大多数英文编码可能不会引起注意,因为在大多数现代编码中,许多字符是相同的)。编码是将数字65
(实际上存储在文件中)转换为字符'A'
的过程。除了编码,还有很多其他复杂性,我将在后面讨论。
您正在使用BinaryWriter
。顾名思义,它是为写入 binary 文件而不是文本文件而设计的。如果要编写纯文本文件,请改用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
)。
但是最后,文件只是一串数字。您需要规则来解释这些数字才有用。选择适合您需求的规则。