实例解析文本文件与二进制文件

 

原文链接:http://www.zhangliancheng.com/2011/04/text-files-vs-binary-files/

 

在使用程序处理文件的时候,总有两个概念是我们无法避开的:二进制文件和文本文件。究竟什么是文本文件,什么是二进制文件,说起来虽然很简单,但能否真正理解其差别并灵活应用在程序之中,却是另外一回事。在这篇文章中,将通过实实在在的程序例子,以及通过它们产生的文件例子,来透彻分析两者之区别,并针对Windows和Linux两个不同平台,以及C标准IO函数和C++标准IO流类的相关方法,作具体分析和比较。

我们知道,作为文件的数据,在计算机中都是以二进制的形式存储的。因此,对于文本文件和二进制文件的区分,不是在物理上的,而是在逻辑上的。然而,不同的逻辑,却可以形成不同的存储内容。这里,先不作过多解释,直接看例子,事实胜于雄辩,例子才是实实在在的。

一、通过实例解析文本文件和二进制文件的区别
在Linux下,编译并运行下面的程序。


 

#include 
#include 

int main(int argc, char *argv[])
{
	FILE *fp;
	int num[5]= {1, 2, 3, 4, 5};

	fp = fopen("textfile.txt", "w+");
	if (!fp)
	{
		fprintf(stderr, "open failed!\n");
		exit(EXIT_FAILURE);
	}
	fprintf(fp, "%s\n", "12345");
	fclose(fp);

	fp = fopen("binfile", "w+");
	if (!fp)
	{
		fprintf(stderr, "open failed!\n");
		exit(EXIT_FAILURE);
	}
	fwrite(num, sizeof(int), sizeof(num)/sizeof(int), fp);
	fclose(fp);

	return 0;
}
运行之后,会在当前目录下生成两个文件(如果原来存在同名文件会被覆盖掉),分别是textfile.txt和binfile。上面的程序在textfile.txt中写入了12345和换行符这样6个字符,并且通过文本查看工具比如gedit或者cat都能看到预想到的结果。

$ cat textfile.txt
12345
$

 

 

而如果试图通过文本查看工具来查看binfile的内容,则只会看到一堆乱码。至此,我们已经确定,textfile.txt正是我们期望的文本文件的样子,即文件中却是存着我们期望的12345这样的字符,同时,binfile也却是不是文本文件的样子,虽然此时还不是很确定是不是我们期望的二进制文件。之所以如此,是因为我们在试图以文本的方式来阅读文件。故此,为了深入细节,我们使用十六进制工具xxd来查看(Windows上面可借助于UltraEdit)。

$ xxd textfile.txt
0000000: 3132 3334 350a                           12345.
$ xxd binfile
0000000: 0100 0000 0200 0000 0300 0000 0400 0000  ................
0000010: 0500 0000                                ....
$

从xxd查看的结果,清晰可见的是,textfile.txt中存储的正是12345以及’\n’对应的ASCII码:0x31对应字符1,0x32对应字符2,…,0x35对应字符5,0x0a对应’\n’。而binfile的十六进制形式,也清晰的展示了,12345这样五个整数正是以二进制的形式存储在其中的:最开始的4个字节0100 0000是整数1,接下来的4个字节是整数2,…,最后的4个字节是整数5。细心的你可能还注意到了另外一个有趣的现象:整数1的十六进制形式0x00000001中低位的1正好存储在了低地址处,而不是高地址处,这正是Little Endian的特点,Intel x86系列CPU的共性。

总结一下这个小实验,我们可以得出这样的结论:文本文件是字符序列,是基于字符编码的,存储的是字符的编码(如ASCII码)序列;二进制文件是字节序列,是基于值编码的,或者说没有什么特别的编码,它同数据在内存中的形式相同,仅仅是二进制数组成的字节序列

二、Windows的文本文件和Linux的文本文件不一样
然而,事情不是这么简单就可以结束的,同样的这个实验,在Windows上来做,就会得出稍微有点不同的结果。

在Windows上,编译运行同样的程序,使用记事本打开textfile.txt,发现正好也是12345这样5个字符;如果尝试使用记事本打开binfile,看到的也是一堆乱码。接下来,我们使用UltraEdit来以十六进制方式查看,其中textfile.txt如下所示:

以十六进制方式查看,其中textfile.txt如下所示:

而binfile在UltraEdit中以十六进制查看的结果是这样的:

从上面的结果来看,binfile在Linux和Windows下没有任何区别,而textfile.txt最后的换行在Linux下是0x0a,而在Windows下却成了0x0d0a,也就是说变成了’\r\n’。

这究竟又是怎么回事呢?这是因为,在Windows下,文本文件的换行被认为是’\r\n’,而我们以文本方式打开文件并写入的时候,系统自动做了这样的转换,就我们写的’\n’转换成了’\r\n’;而Linux下换行就是’\n’,不需要转换,因此在Linux实验的时候并没有出现这个0x0d,0x0d正是回车符’\r’对应的ASCII码。

三、如何打开同是什么文件,两码事
还有一点需要说明,我们前面都是以文本方式打开文件。在调用fopen函数的时候,最后面的mode参数可通过指定带b来采用以二进制的方式打开文件,而前面都没有指定,默认采用的就是以文本方式打开的。下面,我们将调用fopen的那句话改成下面这样:

fp = fopen("textfile.txt", "w+b");

类似的,打开binfile的改成下面这样:

fp = fopen("binfile", "w+b");

然后,再分别在Linux和Windows下各做一次实验。实验后查看结果就会发现,这次在Linux下没有任何变化,也就是说,带不带b在Linux下没有任何影响;而在Windows下,带b以二进制方式打开文件时,UltraEdit查看binfile仍然也没任何变化,但textfile.txt却变成了下面的样子:

也就是说,在使用fopen带b参数以二进制方式打开文件的后,再以文本方式写入换行,系统就不会将’\n’转换为’\r\n’了。

至此,我们可以确定,网上流传的“在Linux下文本文件和二进制文件没有区别”的说法,以及“Windows下文本文件和二进制之间只有一个区别,就是回车加换行和换行的区别”这样的说法,都是有失严谨的。这里,必须明确一点,就是“文本文件、二进制文件”和“以文本方式打开、以二进制方式打开”是完全不同的概念,不可混淆。实验结果给予我们的有充分理由的结论是:是否以二进制方式打开文件,即调用fopen时是否带b,对使用fwrite以二进制方式写入的结果没有影响,不论是在Linux下还是在Windows下;是否以及二进制方式打开,即调用fopen时是否带b,对使用fprintf以文本方式写入的结果仅在Windows下有影响,影响效果就是不带b时会有’\n’到’\r\n’的转换,带b时没有这个转换,对在Linux下的写入没有影响。

四、使用C++的IO流类相关方法重做实验
前面,我们所有实验都是在使用C语言中的IO函数进行的。实际上,也可以使用C++中的IO流类中的相关方法来进行,例如下面的代码:

 

#include 
#include 
#include 
using namespace std;

int main(int argc, char *argv[])
{
	ofstream textfile;
	if (!textfile)
	{
		cerr << "open failed!" << endl;
		exit(EXIT_FAILURE);
	}
	textfile.open("textfile.txt", ios::out);
	//textfile.open("textfile.txt", ios::out | iso::binary);
	textfile << 1 << 2 << 3 << 4 << 5 << endl;
	textfile.close();

	ofstream binfile;
	int data[5] = {1, 2, 3, 4, 5};
	binfile.open("binfile" ios::out);
	//binfile.open("binfile" ios::out | iso::binary);
	if (!textfile)
	{
		cerr << "open failed!" << endl;
		exit(EXIT_FAILURE);
	}
	binfile.write((char *)data, sizeof(data));
	binfile.close();

	return 0;
}


 

这里相当于用<<替换fprintf,用binfile.write替换fwrite,用iso::binary替换fopen中的带b,分别在Linux和Windows下做相同的实验。通过实验,也会得到类似的结论。

好的,到这里,连我都觉得算是折腾够了。全面总结一下,文本文件和二进制文件在Windows和Linux下都是不同的,文本文件是字符序列,存储的是基于字符的编码;而二进制文件则存储的就是对应的值,是字节序列。以文本方式(fopen时不带b,ofstream.open时不带iso::binary)或以二进制方式(fopen时带着b,ofstream.open时设置了iso::binary)打开,同文本文件或者二进制文件是根本无关的概念。以什么方式打开,对于Linux没有不同,只对Windows不同,并且不同也仅仅影响到在Windows下以文本方式写入,具体情况是,以文本方式打开时,以文本方式写入后系统会自动将换行('\n')转换成回车加换行('\r\n'),而以二进制方式打开时,不会作这样的转换,仅此而已

 

 

 

 

 

 

 

 

你可能感兴趣的:(c/c++)