最新在进行文件方面的功能开发。遇到个这样的问题:(1)文件读到中间,然后进行一些修改,(2)然后将文件从修改后的地方截断。本以为这是个简单的操作,却花费了好大的功夫(网上并没有这样的例子,一通尝试)。现在终于圆满解决了,特地记录一下,方便后来人。

1.修改/覆盖指定位置的文件内容

【ps】下文一直提到文件中间区域位置,就是指非文件开头和结尾的位置。为什么强调这个呢?因为开头和结尾就是很常规的就成功了,而非开头和结尾的位置则有注意点才能成功。

开头提到的问题(1),即文件读到中间或者其他位置,对内容进行一些修改。所谓修改,即覆盖原文件那个位置的内容,文件大小并不发生变化。

ofstream在打开文件时默认清空文件所有内容。如果使用ios::app来打开文件,虽然不会清空文件内容,但是每次写操作都追加到文件末尾,即使你seekp也没用。

#include<fstream>
using namespace std;

int main()
{
	fstream fs("F:\\test.txt", ios::binary | ios::out | ios::app);
	//跳转到开头的第二个字节位置进行写入,最后发现还是写在结尾,即使seekp也没用。
	fs.seekp(2,ios::beg);
	fs.write("!!!", 3);
	fs.close();
	return 0;
}

运行结果:开始text.text 内容是abcdefgh。现在变为abcdefgh!!!。无效,app模式是一定写在后面的,seekp也无效。

解决办法是使用 fstream 并且再加个文件打开模式ios::app替换为ios::in,这样可以保证文件内容不会被清空,且文件指针偏移操作有效。

下面是正确操作:

#include<fstream>
using namespace std;

int main()
{
	fstream fs("F:\\test.txt", ios::binary | ios::out | ios::in);
	//跳转到开头的第二个字节位置进行写入,正常写入
	fs.seekp(2,ios::beg);
	fs.write("!!!", 3);
	fs.close();
	return 0;
}

运行结果:开始text.text 内容是abcdefgh。现在变为ab!!!fgh。成功实现对文件的中间区域进行修改。

【注意点】:中间的位置必须使用feekp然后再写才能成功修改。并且seekp之后,如果继续读一些内容,然后再写也写不进去。必须seekp之后就写,才写的进去。看下面例子就明白了。

#include<fstream>
using namespace std;

int main()
{
	fstream fs("F:\\test.txt", ios::binary | ios::out | ios::in);
	//文件指针正常到第二个字节,然后进行写入,发现写不进去。
	char buf[2];
	fs.read(buf, sizeof(buf));
	fs.write("!!!", 3);
	fs.close();
	return 0;
}

运行结果:开始text.text 内容是abcdefgh。现在还是abcdefgh。写不进去啊。一开始就是这样操作的,一脸懵。就像上面说的,在fs.read的下一行加个feekp,然后再写就好了。看到这里你应该明白上面的注意点是什么意思了。

2.从某个位置开始截断文件

以前的c++标准库里面是不提供这个功能的,只能依赖操作系统的api。随着c++17标准库中加入了filesystem,其中有个resize_file函数,便十分方便的截断文件。

定义于头文件 <filesystem>
void resize_file(const std::filesystem::path& p,
                 std::uintmax_t new_size);
void resize_file(const std::filesystem::path& p,
                 std::uintmax_t new_size,
                 std::error_code& ec) noexcept;

更改 p 所指名的的常规文件大小。
若先前的文件大小大于 new_size ,则文件的剩余部分被舍弃。
若先前的文件大小小于 new_size ,则增加文件大小,而且新区域如同以零填充。

 

从某个位置截断文件,即只要这么大的文件,然后用这个size进行 resize_file截断就好了。下面是正确的演示:

#include<fstream>
#include<filesystem>
using namespace std;

int main()
{
	//5字节处进行截断,即只有5字节大小
	filesystem::resize_file("F:\\test.txt", 5);
	return 0;
}

运行结果:开始text.text 内容是abcdefgh。现在是abcd。正确的从第五个字节进行截断。

【ps】如果不支持c++17,则使用系统api。

//linux系统
#include <unistd.h>
 int ftruncate(int fildes, off_t length);
 int truncate(const char *path, off_t length); 

//windows系统
int _chsize( int fd,long size );