最新在进行文件方面的功能开发。遇到个这样的问题:(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 );