项目场景:
有两个txt文件,分别命名为flag1.txt和flag2.txt,分别存有64个整数。要求读取两个文件的数据,把所有数据合并存入一个新txt文件中。
问题描述:
使用了fread()和fwrite(),开了一个整数类型的数组buf[]暂存读取和写入的数据。程序运行一切正常,但是最终结果却是生成的文件3.txt中的数据缺了两个,分别是flag1.txt和flag2.txt的最后一个数。
问题代码:
#include <stdio.h>
#include <stdlib.h>
int merge(const char *in1, const char *in2, const char *out);
int main()
{
int flag;
flag = merge("flag1.txt", "flag2.txt", "3.txt");
if(!flag)
printf("Failed!");
else
printf("Successfully!");
return 0;
}
int merge(const char *in1, const char *in2, const char *out)
{
FILE *f_in, *f_out;
int buf[100];
int n;
f_out = fopen(out, "wb");
if(!f_out)
return 0;
f_in = fopen(in1, "rb");
if(!f_in)
return 0;
while((n=fread(buf,sizeof(int),sizeof(buf)/sizeof(int),f_in)))
{
fwrite(buf,sizeof(int),n,f_out);
}
fclose(f_in);
f_in = fopen(in2, "rb");
if(!f_in)
return 0;
while((n=fread(buf,sizeof(int),sizeof(buf)/sizeof(int),f_in)))
{
fwrite(buf,sizeof(int),n,f_out);
}
fclose(f_in);
fclose(f_out);
return 1;
}
输出结果是Successfully! 但是最后缺了两个数 如flag1.txt中最后一个数32671,在合并到3.txt文件中后变成了326。
原因分析:
我一开始以为是数据类型设置不对,可能是32671这个数太大了,超过了int类型可以表达的最大数。但是上网查阅后发现现在的int类型可以表达2的32次方这样大的数了。并非如此
后来试了一下,发现还是IO流的问题。我的读写格式设置的不对,而且代码里是分别对两个文件进行读取,对3.txt文件写入了两次。为了避免出错我在第一次写入后就关闭了3.txt这个文件,然后重新打开。
添加了这么两行:
fclose(f_out);
f_out = fopen(out, "wb");
修改完后测试,发现确实能把最后一个整数完整的读取和写入3.txt,但是最后3.txt文件中只显示了flag2.txt文件中的整数,没有显示flag1.txt的整数。
很快我发现问题出在读写格式上 我原来设置的读写格式是"wb"。这个格式的特点是以二进制打开文件,可读可写。但是每次都是从文件开头读写。如果原来存在写入的数据,就会被覆盖掉。于是,我修改了第二次打开3.txt文件的写入格式。在子函数里把第二个"wb"改成了"ab",这一格式也是以二进制打开文件,可读可写,但是如果文件原来存在数据会接在文件的末尾,接着写入新的数据
f_out = fopen(out, "ab");
这一下,成功解决了读写文件末尾数据写入不全的问题,并且成功合并写入了两个文件中的数据。但是,又有一个很让人抓狂的现象,写入的两组来自不同文件的整数之间竟然没有空格!第一组的最后一个数和第二组的第一个数竟然合在了一起。
我决定再在这两组数之间加一个空格。于是又加了这么两行
char space[] = {' '};
fwrite(space,1,1,f_out);
然而,正当我满心欢喜以为我解决了这个问题的时候。隔了一天,一个漂亮小姐姐copy了这一段修改去尝试。她告诉我在她的电脑文本编辑器里又出现了最开始遇到的文件末尾数据读写不全的问题。
原来,虽然文件都是以二进制的形式存在的,但是txt文件普遍使用ASCII或者其他所谓通用编码,如果直接以二进制格式读写,还像我原来一样按数据块每四个字节读写一次,那么就很容易引入一些编码中的换行符、休止符、制表符之类符号引起的错误。更加别提微软记事本会在文本文件开头引入格式前缀的问题了。
虽然我们直接打开文本文件看不到这些符号和前缀,但用fread()和fwrite()读写的时候都会读取这些内容。
因此无需像我之前的那段代码这么麻烦。所有读写格式里的"b"去掉。
f_out = fopen(out, "a");
if(!f_out)
return 0;
f_in = fopen(in1, "r");
if(!f_in)
return 0;
另外,请不要随便按照超过一个字节的数据块的形式读取数据。还是老老实实一个字节一个字节地读取。
fwrite()和fread()函数的功能实际上很强大,它支持按照数据块的形式读写,但是对于超过一个字节大小的数据类型,你按照每单个字节来读写一次的方式进行读写也是可以的。
while((n=fread(buf,1,sizeof(buf),f_in)))
{
fwrite(buf,1,n,f_out);
}
这样写的好处是,无论什么编码格式,每个字符所占的空间不会小于一个字节,单字节读写能避免很多格式错误的引入。
解决方案:
修改后的完整代码如下:
#include <stdio.h>
#include <stdlib.h>
int merge(const char *in1, const char *in2, const char *out);
int main()
{
int flag;
flag = merge("flag1.txt", "flag2.txt", "3.txt");
if(!flag)
printf("Failed!");
else
printf("Successfully!");
return 0;
}
int merge(const char *in1, const char *in2, const char *out)
{
FILE *f_in, *f_out;
int buf[100];
int n;
f_out = fopen(out, "a");
if(!f_out)
return 0;
f_in = fopen(in1, "r");
if(!f_in)
return 0;
while((n=fread(buf,1,sizeof(buf),f_in)))
{
fwrite(buf,1,n,f_out);
}
char space[] = {' '};
fwrite(space,1,1,f_out);
fclose(f_in);
fclose(f_out);
f_out = fopen(out, "a");
if(!f_out)
return 0;
f_in = fopen(in2, "r");
if(!f_in)
return 0;
while((n=fread(buf,1,sizeof(buf),f_in)))
{
fwrite(buf,1,n,f_out);
}
fclose(f_in);
fclose(f_out);
return 1;
}
啊,世界终于清静了~~搞定
|