二进制模式和文本模式下的文件操作——性能问题

15

在许多项目中,我看到数据对象/结构以二进制模式写入文件,再以二进制模式从文件中检索它们。

我想知道为什么他们要以二进制模式进行操作?文本模式和二进制模式之间有任何性能差异吗?如果没有,那么什么情况下使用二进制模式或文本模式?


我怀疑这是 https://dev59.com/qXVC5IYBdhLWcg3woStW 的重复,但我不确定。 - jogojapan
@jogojapan,差不多。但是那篇帖子并没有完全回答我的问题。 - Alcott
要读取/使用二进制文件,必须了解它的结构。 - Adam
7个回答

22

二进制更快。考虑一个存储在32位(4字节)中的整数,例如123456。如果以二进制形式写出它(这就是计算机中的表示方法),它将占用4个字节(忽略结构对齐时项目之间的填充)。

如果要将数字写成文本,则必须将其转换为一串字符(有些开销需要进行转换和内存存储),然后再将其写出,至少需要6个字节,因为有6个字符来表示数字。这还不包括任何额外的填充,如空格以对齐或分隔数据。

现在,如果您考虑到您有数千个项目,附加时间可能会增加并且需要更多的空间,这将需要更长的时间来读入,然后在将值读入内存后进行二进制转换时需要额外的时间。

文本的优点在于,与尝试阅读二进制数据或数据的十六进制转储相比,它对于人来说更容易阅读。


3
我发现你的回答更容易理解。 :-) - Alcott

7
如果您的程序是唯一使用该文件的程序,那么可以使用二进制文件将内部结构“原样”保存。
然而,如果您想与其他程序或通过互联网交换文件,则二进制格式并不是很好。例如,请考虑大端和小端机器之间的问题。此外,接收文件或数据的人很可能无法访问您的代码和结构,因此基于文本的格式可能更容易解析并实现到自己的结构中。
关于性能,读写内部结构直接会更快,因为您不需要将它们翻译(也称为编组)成另一种格式。

+1。就像你指出的一样,我是唯一一个使用这些数据对象/结构的人,并且我想将它们保存到文件中并检索回来。在这种情况下,我不认为文本文件有帮助,因为所谓的“文本文件”,你指的是我应该将每个数据对象/结构的值以纯文本形式写入文件,然后读取这些文本,并将它们用作构建原始数据对象的值? - Alcott
@Alcott 如果您是唯一读写这些文件的人,则可以使用二进制格式,直接读写结构。但是,请注意指针!写入包含指针的结构时,要写入实际指针值,而不是它所指向的内容。稍后读取时,它将指向某个未分配的内存区域。此外,在读写字符串时,请考虑终止字符 '\0' - Some programmer dude
谢谢这些技巧。如果我使用二进制模式将这些数据结构写入文件,然后再使用文本模式读取文件,我仍然可以获得我放入文件中的东西吗? - Alcott
@Alcott 以文本模式打开文件可能导致读取或写入某些字符进行一些“转换”。最明显的是,换行符可能会在'\n'"\r\n"之间进行转换。因此,如果文件中有与'\n'(10进制)对应的某些值,则在读取时可能会返回两个字节而不是一个(13和10)。 - Some programmer dude
他问的是二进制模式,而不是二进制格式(尽管他可能真的不理解两者之间的区别)。如果你要写入互联网,你需要使用二进制模式,而不是文本模式,因为你需要控制像行结束符这样的表示方式。 - James Kanze
显示剩余3条评论

7

历史上,二进制模式提供了对底层流的更或多或少透明的访问;文本模式“标准化”为一个标准文本表示,其中行以单个'\n'字符终止。此外,系统可能会对二进制文件的大小施加限制,例如要求它是128或512字节的倍数。(第一个是CP/M的情况,第二个是许多DEC操作系统的情况。)文本文件没有这种限制,在操作系统强制执行该限制的情况下,库通常会为文本文件引入附加的文件结束符字符。(即使在今天,大多数Windows库在文本模式下读取时仍会识别旧的CP/M文件结束符0x1A。)由于这些考虑因素,文本模式仅定义了一组有限的二进制值。(但是如果你向一个二进制文件写入200字节,当你重新读取它时,你可能会得到256或512字节。从历史上看,二进制应该仅用于其他结构化的文本,以便你可以识别逻辑结束并忽略这些附加字节。)

此外,在二进制模式下,您可以任意寻找文件;而在文本模式下,您只能寻找到开头或先前记忆的位置。(这是因为行结束映射意味着文件中的位置与文本流中的位置之间没有简单的关系。)

请注意,这与输出是否格式化无关:如果您使用<<(和使用>>输入),则IO是格式化的,而不管文件以哪种模式打开。并且格式化始终是文本的;iostreams旨在操作文本流,并且仅对非文本输入和输出提供有限的支持。

今天,情况有所改变:在许多情况下,我们希望我们编写的内容可以从其他计算机上读取,这需要一个定义良好的格式,该格式可能与本地使用的格式不同。(例如,互联网期望两个字节序列0x0D、0x0A作为行结束符,这与Unix和许多其他操作系统内部使用的不同。)如果可移植性是一个问题,您通常会定义一个格式,明确地编写它,并使用二进制模式来确保您编写的内容正是所写的内容;类似地,在输入时,您使用二进制格式,并手动处理约定。但是,如果只是写入到本地磁盘,而不共享,则文本模式就可以了,而且更少工作。

再次强调,这两种方式都适用于文本。如果你想要二进制格式,必须使用二进制模式,但这远远不够。你需要自己实现所有的格式化IO。在这种情况下,我通常不使用std::istreamstd::ostream(它们的抽象是文本),而是定义自己的流类型,从std::ios_base派生(用于错误处理约定),并使用std::streambuf(用于物理IO)。

最后,请不要忽视一个事实,即所有IO都以某种方式进行格式化。仅将一块内存写入文件意味着该格式是当前实现所给出的任何格式(通常未经记录,这意味着您可能无法在将来阅读它)。如果你只是将其溢出到磁盘上,并且你唯一读取它的时间是使用相同程序、编译器版本和编译器选项编译的同一程序,则可以仅转储内存,前提是所涉及的内存仅为POD,并且不包含指针。否则,您必须定义(并记录)您使用的格式,并实现它。在这种情况下,我建议使用现有的格式,如XDR,而不是发明自己的格式:编写"使用XDR格式"作为文档要容易得多,而不是描述所有不同类型的位和字节布局。


谢谢你的详细回答,但我不能说我完全理解,:-)。为什么我不能在文本模式下任意查找?使用seekg(pos),我几乎可以寻找文件的每个位置,对吗? - Alcott
1
@Alcott 因为标准规定它是未定义行为。如果pos是从tellg()调用返回的值,或者pos0,那么就没有问题。否则,它是未定义行为。(实际上,在Unix下它可以工作,在Windows下它会把你略微超前于你想去的地方。在其他操作系统下呢?谁知道呢。) - James Kanze

3
如果你以文本模式读写文件,那么你正在处理文本。这可能会导致编码错误和特定于操作系统的格式更改,但有时它也可以正常工作。但是,在二进制模式下,你不会遇到这些限制。此外,文本模式可能会对'\ n'字符执行有趣的操作,例如用'\n\r'替换它们。
例如,Fopen参考文献中指出:
“在文本文件的情况下,根据应用程序运行的环境,在输入/输出操作中可能会发生一些特殊的字符转换,以使它们适应特定于系统的文本文件格式。在许多环境中,例如大多数基于UNIX的系统,以文本文件或二进制文件打开文件没有区别;但为了更好的可移植性,建议进行区分。”

这种替换会降低一些性能,因为代码必须检查每个字符。 - Tobias Langner
@TobiasLangner,所以\n/\r\n的替换会成为性能问题吗? - Alcott

2
在二进制模式下,您可以利用一个字节的大小(考虑256),而在文本模式下,几乎只有100个字符。 显然,您将获得超过两倍的存储数据的大小。
此外,还有一些情况需要遵守结构规范,例如像IPv4这样的网络数据包。

让我们举个例子。

//No padding
typedef struct abc
{
 int a:4
 char b;
 double c;
} A[]={{.a=4,.b='a',.c=7.45},{.a=24,.b='z',.c=3.2}} ;

在文本模式下存储位字段不是很困难吗?显然你会失去很多东西。

但是,您可以像使用MIME一样以文本格式保存数据对象,但这将需要额外的例程来转换为二进制模式;性能受到影响。


你的意思是用文本模式将A写入文件更好吗?如果是这样,怎么做呢?只需将每个数据成员的值写入文件中作为纯文本,然后读取值以创建数据对象即可。 +1 for the code. - Alcott
:) 这可能会很困难,您可以使用名为XML的一种方法以文本模式编写,例如<XML><STRUCT><Instance n="0" type="Text"><val attr="a" bit field="4">4</val></Instance></STRUCT></XML>,但最终您必须将其转换为二进制进行正常操作。在正常的二进制中,只需将结构的值转储到文件中即可。在读取操作期间,如果目标结构符合规格,则无需担心如何读取。随着光标向前移动,数组将不断填充。 - perilbrain

2
只有少数操作系统受到二进制模式和文本模式之间选择的影响。Unix或Linux系统对于文本模式不做任何特殊处理,也就是说,文本与二进制相同。
尤其是Windows和VMS在文本模式下会转换数据。Windows在写入文件时将\n转换为\r\n,在读取时则相反。VMS具有文件记录结构需要遵守,在默认模式下,它将\n转换为记录分隔符。
当它们不同时,使用二进制更快。如果它们没有区别,则没有任何差异。

如果它们不同,性能差异会很明显吗? - Alcott
@Alcott:在普通情况下,我不会期望性能上有显著的差异。然而,通过大量使用\n并轻微使用其他内容,很容易构建出一个存在显著差异的测试。最糟糕的情况是,Windows会使写入的数据量增加一倍,而VMS则会疯狂地创建大量记录。 - wallyk

0

二进制格式更准确地存储数字,因为它们以精确的内部表示方式存储。在保存数据时没有转换,因此保存速度更快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接