将数据保存到二进制文件

3

我想将文件保存为二进制,因为我听说它可能比普通文本文件更小。

现在我正在尝试保存带有一些文本的二进制文件,但问题是该文件只包含文本和结尾处的NULL。我期望在文件内部只看到0和1。

非常感谢任何解释或建议。

这是我的代码

#include <iostream>
#include <stdio.h>

int main()
{
     /*Temporary data buffer*/
     char buffer[20];

     /*Data to be stored in file*/
     char temp[20]="Test";

     /*Opening file for writing in binary mode*/
     FILE *handleWrite=fopen("test.bin","wb");

     /*Writing data to file*/
     fwrite(temp, 1, 13, handleWrite);

     /*Closing File*/
     fclose(handleWrite);

    /*Opening file for reading*/
    FILE *handleRead=fopen("test.bin","rb");

    /*Reading data from file into temporary buffer*/
    fread(buffer,1,13,handleRead);

    /*Displaying content of file on console*/
    printf("%s",buffer);

    /*Closing File*/
    fclose(handleRead);
    std::system("pause");

    return 0;
}

这是 C 而不是 C++,除了你的一个 std::system 调用。 - Tony The Lion
好的,那就是C语言。但为什么它不工作呢? - Datoxalas
6个回答

15

所有文件只包含二进制的1和0,在计算机中这是可以操作的唯一内容。

当你保存文本时,你实际上是在保存该文本的二进制表示,采用给定的编码来定义每个字母映射到哪些位。

因此对于文本,无论是文本文件还是二进制文件都几乎没有区别;你所听说的节省空间通常适用于其他数据类型。

考虑一个浮点数,例如3.141592653589。如果以文本形式保存,每个数字将占用一个字符(只需数一下),加上小数点。如果以二进制形式保存为float的位副本,它将在典型的32位系统上占据四个字符(四个字节或32位)。通过调用如下函数存储的确切位数:

FILE *my_file = fopen("pi.bin", "wb");
float x = 3.1415;
fwrite(&x, sizeof x, 1, my_file);

CHAR_BIT * sizeof x,请参考 <stdlib.h> 中的 CHAR_BIT


谢谢你的解释。 - Datoxalas

14
您所描述的问题是一系列(非常常见1但可惜)的错误和误解。让我尝试详细说明正在发生的事情,希望您能花时间阅读所有材料:内容很长,但这些都是任何程序员都应该掌握的非常重要的基础知识。如果您没有完全理解其中的所有内容,请不要绝望:只需尝试玩弄它,一周或两周后回来,练习一下,看看会发生什么🙂。
字符“编码”和字符“集合”的概念之间有一个关键区别。除非您真正理解这种差异,否则您永远无法真正理解正在发生的事情。Joel Spolsky(Stackoverflow的创始人之一)在一篇文章中解释了这种差异:The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)。在继续阅读本文,在继续编程之前,首先阅读该文章。说实话,阅读并理解它:标题并非夸张。您必须绝对了解这方面的知识。
之后,我们来继续:
当一个 C 程序运行时,一个用于保存类型为“char”的值的内存位置,与任何其他内存位置一样,都包含一个由1和0组成的序列。变量的“类型”只对编译器有意义,对于只看到1和0而不了解更多内容的运行程序而言并没有什么意义。换句话说,当您通常认为“字母”(字符集合中的元素)驻留在某个内存位置时,实际上存在的是一个二进制位序列(字符编码中的元素)。
每个编译器都可以自由地使用它所选择的任何编码来表示内存中的字符。因此,它可以把我们称为“换行符”的东西内部表示为任何它选择的数字。例如,假设我编写了一个编译器,我可以约定每次想要将“换行符”存储在内部时,我将其存储为数字6,这只是二进制中的0x6(或110)。
写入文件是通过同时告诉操作系统2四件事情来完成的:
  • 您要写入文件(fwrite()
  • 您要写入的数据从哪里开始(作为 fwrite 的第一个参数)
  • 您要写入多少数据(第二个和第三个参数相乘)
  • 您要写入哪个文件(最后一个参数)
注意,这与数据的“类型”无关:您的操作系统不知道也不关心字符集,并不知道任何有关字符集的信息。它只看到从某个位置开始的一系列二进制数字,并将其复制到文件中。
以“二进制”模式打开文件实际上是处理文件的正常直观方式,一个初学者程序员所期望的方式:你指定的内存位置会被一对一地复制到文件中。如果你写入一个曾经存储了编译器决定存储为“char”类型的变量的内存位置,那么这些值将会被一对一地写入到文件中。除非你知道编译器如何在内部存储值(它将换行符、字母'a'、'b'等与哪个值相关联),否则这是没有意义的。将这与乔尔关于文本文件没有了解其编码就是无用的类似观点进行比较:同样的问题。
在“文本”模式下打开文件几乎等同于二进制模式,只有一个(且仅有一个)区别:每当写入一个值等于编译器内部使用的换行符值(6,在我们的例子中),它就会将不同于该值的内容写入文件:不是该值,而是操作系统认为的换行符。在Windows中,这是两个字节(13和10,或0x0d 0x0a)。请再次注意,如果您不知道编译器的内部表示方式,则这仍然是没有意义的。
在这一点上,请注意,除了编译器指定为字符的数据之外,以文本模式将任何东西写入文件都是一个坏主意:在我们的例子中,一个6可能只是你要写入的值之一,在这种情况下,输出会以我们绝对不想要的方式被更改。
(不)幸运的是,大多数(全部?)编译器实际上都使用相同的字符内部表示:这个表示是US-ASCII,它是所有默认值的原型。这就是您可以编译带有任意随机编译器的程序并将某些“字符”写入文件,然后使用文本编辑器打开它的原因:它们都使用/理解US-ASCII,并且它们恰好可以工作的原因。
现在来把这与您的示例联系起来:为什么在二进制模式下和文本模式下写入“test”没有区别?因为“test”中没有换行符,这就是为什么!
当您“打开文件”然后“看到”字符时,这意味着您用于检查该文件中的一系列二进制数字的程序(因为硬盘上的所有内容都是一系列二进制数字)决定将其解释为US-ASCII,并且这恰好是您的编译器在其内存中对该字符串进行编码的方式。加分项:编写一个程序,将文件中的二进制位读入内存,并将每个BIT(一个字节由多个位组成,要提取它们,您需要知道“按位”运算符技巧,请谷歌!)作为“1”或“0”打印给用户。请注意,“1”是字符1,在您选择的字符集中,因此您的程序必须将一位(数字1或0)转换为表示终端仿真器使用的编码中字符1或0所需的位序列,以便在上面查看程序标准输出的终端仿真器。好消息是:您可以假设在所有地方都使用US-ASCII而采取许多捷径。该程序将向您展示您想要的内容:编译器在内部表示“测试”的位序列。
对于新手来说,这些东西确实很令人望而生畏,我知道即使是我也花了很长时间才知道字符集和编码之间的区别,更不用说它们是如何工作的了。希望我没有让你失去动力,如果我这样做了,只要记住您永远无法失去已经拥有的知识,只能获得它(好的,不总是正确的:P)。在生活中,一个陈述引发更多问题而不是回答是很正常的,苏格拉底知道这一点,他的智慧无缝地适用于现代技术2400年后。
祝你好运,继续提问。对其他读者:如果您看到错误,请随时改进此帖子。
Hraban
1. 告诉您“以二进制形式保存文件可能更小”的人,例如,可能严重误解了这些基础知识。除非他指的是在保存之前压缩数据,否则他只是使用“二进制”来代替“压缩”的一个令人困惑的词语。 2. “告诉操作系统某些信息”通常称为系统调用。

3

嗯,本地和二进制之间的区别在于处理行尾的方式。 如果您在二进制中编写字符串,它将保持原样。

如果您想使其更小,则必须以某种方式压缩它(例如查找 libz)。

更小的是:当要保存二进制数据(如字节数组)时,将其保存为二进制比将其放入字符串中(在十六进制表示或 base64 中)更小。希望这可以帮助到您。


你是指 zlib 还是 libz - Datoxalas
我在谈论zlib,抱歉打字太快了 :-) - Bruce
你能帮我一下吗?关于这个问题:https://dev59.com/S2035IYBdhLWcg3wC7qf - Datoxalas

1

我觉得你有点困惑了。

当你将ASCII字符串“Test”写入文件时(即使在二进制模式下),它仍然是ASCII字符串。只有在写入除字符之外的其他类型(例如整数数组)时,才有意义使用二进制模式。


0

尝试替换

FILE *handleWrite=fopen("test.bin","wb");
fwrite(temp, 1, 13, handleWrite);

使用

FILE *handleWrite=fopen("test.bin","w");
fprintf(handleWrite, "%s", temp);

我得到了相同的结果。 - Datoxalas
仍然得到相同的结果。 - Datoxalas

0

函数 printf("%s",buffer); 会将 buffer 作为以零结尾的字符串打印出来。

试着使用: char temp[20]="测试\n\r测试";


我这里并没有询问任何关于 printf() 的问题。 - Datoxalas

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