我需要一个跨平台的便携式功能,能够将100GB +二进制文件复制到新目的地.我的第一个解决方案是:
void copy(const string &src, const string &dst)
{
FILE *f;
char *buf;
long len;
f = fopen(src.c_str(), "rb");
fseek(f, 0, SEEK_END);
len = ftell(f);
rewind(f);
buf = (char *) malloc((len+1) * sizeof(char));
fread(buf, len, 1, f);
fclose(f);
f = fopen(dst.c_str(), "a");
fwrite(buf, len, 1, f);
fclose(f);
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,该计划非常缓慢.我怀疑缓冲区必须在内存中保持100GB +.我很想尝试新代码(从一个健全,安全,有效的方式复制文件):
std::ifstream src_(src, std::ios::binary);
std::ofstream dst_ = std::ofstream(dst, std::ios::binary);
dst_ << src_.rdbuf();
src_.close();
dst_.close();
Run Code Online (Sandbox Code Playgroud)
我的问题是关于这一行:
dst_ << src_.rdbuf();
C++标准对此有何评论?代码是编译为逐字节传输还是只是整个缓冲区传输(就像我的第一个例子)?
我很好奇<<编译成对我有用的东西?也许我不必把时间花在其他事情上,让编译器在运营商内部完成工作?如果操作员转换为我的循环,我为什么要自己做?
PS:std::filesystem::copy不可能,因为代码必须适用于C++ 11.
问题的关键是当你这样做时会发生什么:
dst_ << src_.rdbuf();
Run Code Online (Sandbox Code Playgroud)
显然,这是两个函数调用:一个istream::rdbuf()只是返回指向a的指针streambuf,后面跟一个指向ostream::operator<<(streambuf*),其记录如下:
构造并检查sentry对象后,检查sb是否为空指针.如果是,则执行setstate(badbit)并退出.否则,从sb控制的输入序列中提取字符并将其插入*this,直到满足下列条件之一:[...]
读到这个,你的问题的答案是以这种方式复制文件不需要缓冲内存中的整个文件内容 - 而是一次读取一个字符(可能有一些分块缓冲,但这是一个优化,不应该不要改变我们的分析.
这是一个实现:https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.6/a01075_source.html(__copy_streambufs).基本上它是一个循环调用sgetc()并sputc()重复直到达到EOF.所需的内存很小且不变.
C++标准(我检查了C++ 98,所以这应该是非常兼容的)在[lib.ostream.inserters]:
Run Code Online (Sandbox Code Playgroud)basic_ostream<charT,traits>& operator<< (basic_streambuf<charT,traits> *sb);
效果:如果
sb是空调用setstate(badbit)(可能会抛出ios_base::failure).从中获取字符
sb并将其插入*this.读取sb和插入字符,直到出现以下任何一种情况:
- 文件结束发生在输入序列上;
- 插入输出序列失败(在这种情况下,不插入要插入的字符);
- 从中获取角色时会发生异常
sb.如果函数没有插入任何字符,则调用
setstate(failbit)(可以抛出ios_base::failure(27.4.4.3)).如果在提取字符时抛出异常,则设置failbit为错误状态的函数,如果failbit在exceptions()捕获的异常中处于启用状态,则重新抛出该异常.返回:
*this.
这说明说<<上rdbuf一个字符一个字符地工作.特别是,如果插入一个字符失败,那么该输入序列中的确切字符仍未读取.这意味着实现不能只是将整个内容提取到一个巨大的缓冲区中.
所以是的,在标准库的内部有一个循环,它进行逐字节(好的,charT真正的)传输.
但是,这并不意味着整个事情完全没有缓冲.这只是关于operator<<内部的内容.您的ostream对象仍将在内部累积数据,直到其缓冲区已满,然后调用write(或您的操作系统使用的任何低级函数).
| 归档时间: |
|
| 查看次数: |
149 次 |
| 最近记录: |