以明智、安全和高效的方式复制一个文件
以明智、安全和高效的方式复制一个文件
我正在寻找一种好的方法来复制文件(二进制或文本)。我写了几个示例,每个都可以工作。但是我想听听经验丰富的程序员的意见。
我需要好的示例,并且正在寻找一个适用于C++的方法。
ANSI-C-WAY
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE的默认值是8192个字节
// BUFSIZE为1表示每次一个字符
// 好的值应该适合块大小,如1024或4096
// 较高的值可以减少系统调用的次数
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
FILE* source = fopen("from.ogv", "rb");
FILE* dest = fopen("to.ogv", "wb");
// 清除并更加安全
// feof(FILE* stream)如果 stream 的文件结束指示器被设置,则返回非零值
while (size = fread(buf, 1, BUFSIZ, source)) {
fwrite(buf, 1, size, dest);
}
fclose(source);
fclose(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
POSIX-WAY(K&R在《C程序设计语言》中使用这种方法,更底层)
#include
#include
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE的默认值是8192
// BUFSIZE为1表示每次一个字符
// 好的值应该适合块大小,如1024或4096
// 较高的值可以减少系统调用的次数
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
while ((size = read(source, buf, BUFSIZ)) > 0) {
write(dest, buf, size);
}
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
KISS-C++-Streambuffer-WAY
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
dest << source.rdbuf();
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
COPY-ALGORITHM-C++-WAY
#include
#include
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
istreambuf_iterator
istreambuf_iterator
ostreambuf_iterator
copy(begin_source, end_source, begin_dest);
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
OWN-BUFFER-C++-WAY
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
// 文件大小
source.seekg(0, ios::end);
ifstream::pos_type size = source.tellg();
source.seekg(0);
// 分配缓冲区的内存
char* buffer = new char[size];
// 复制文件
source.read(buffer, size);
dest.write(buffer, size);
// 清理
delete[] buffer;
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
LINUX-WAY // 需要内核版本 >= 2.6.33
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
clock_t start, end;
start = clock();
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
// 需要的结构,原因:函数stat()也存在
struct stat stat_source;
fstat(source, &stat_source);
sendfile(dest, source, 0, stat_source.st_size);
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast
return 0;
}
环境
GNU/LINUX(Archlinux)
内核3.3
GLIBC-2.15,LIBSTDC++ 4.7(GCC-LIBS),GCC 4.7,Coreutils 8.16
使用RUNLEVEL 3(多用户,网络,终端,无GUI)
INTEL SSD-Postville 80 GB,填充到50%
复制一个270 MB的OGG-VIDEO-FILE
复现步骤
1. $ rm from.ogg
2. $ reboot # 内核和文件系统缓冲区处于常规状态
3. $ (time ./program) &>> report.txt # 执行程序,将程序的输出重定向并追加到文件中
4. $ sha256sum *.ogv # 校验和
5. $ rm to.ogg # 删除副本,但不进行同步,使用内核和文件系统缓冲区
6. $ (time ./program) &>> report.txt # 执行程序,将程序的输出重定向并追加到文件中
结果(使用的CPU时间)
Program Description UNBUFFERED|BUFFERED
ANSI C (fread/frwite) 490,000|260,000
POSIX (K&R, read/write) 450,000|230,000
FSTREAM (KISS, Streambuffer) 500,000|270,000
FSTREAM (Algorithm, copy) 500,000|270,000
FSTREAM (OWN-BUFFER) 500,000|340,000
SENDFILE (native LINUX, sendfile) 410,000|200,000
文件大小没有改变。
sha256sum打印相同的结果。
视频文件仍然可以播放。
问题
- 你更喜欢哪种方法?
- 你知道更好的解决方案吗?
- 你在我的代码中看到任何错误吗?
- 你知道避免使用某种解决方案的原因吗?
- FSTREAM(KISS,Streambuffer)
我真的很喜欢这个方法,因为它非常简洁和简单。据我所知,运算符<<被重载为rdbuf(),不会进行任何转换。正确吗?
谢谢
更新1
我在所有示例中更改了源码,将文件描述符的打开和关闭包含在clock()测量中。源代码中没有其他重大更改。结果没有改变!我还使用time进行了双重检查结果。
更新2
ANSI C示例更改:while循环的条件不再调用feof(),而是将fread()移动到条件中。看起来,该代码现在运行得要快10,000个时钟。
测量更改:以前的结果始终是缓冲的,因为我为每个程序多次重复了旧的命令行rm to.ogv && sync && time ./program。现在我为每个程序重新启动系统。无缓冲结果是新的,并且没有令人惊讶。无缓冲的结果实际上并没有真正改变。
如果我不删除旧的副本,程序的反应会有所不同。使用POSIX和SENDFILE覆盖现有文件的缓冲区更快,而其他所有程序都更慢。也许truncate或create选项对此行为有影响。但是使用相同副本覆盖现有文件不是真实世界的用例。
使用cp进行复制需要0.44秒无缓冲和0.30秒缓冲。因此,cp比POSIX示例稍微慢一些。对我来说看起来不错。
也许我还会添加mmap()和boost::filesystem的copy_file()的示例和结果。
更新3
我将其也放在博客页面上,并稍作扩展。包括splice(),它是Linux内核的低级函数。也许会有更多的Java示例和结果。
http://www.ttyhoney.com/blog/?page_id=69
在这段内容中,讨论了如何以一种理智、安全和高效的方式复制文件。下面是这个问题的出现原因和解决方法的整理:
- 在ANSI C中,使用缓冲区进行文件复制是多余的,因为FILE
已经有了缓冲区。可以通过BUFSIZ
定义的内部缓冲区大小来使用FILE
的缓冲功能。
- 使用fstream
的"OWN-BUFFER-C++-WAY"方法会很慢,因为它需要进行大量的虚拟分派,并且为每个流对象维护内部缓冲区。而使用streambuf_iterator
类可以绕过流层,因此"COPY-ALGORITHM-C++-WAY"方法不会受到这个问题的影响。
- 更推荐使用"COPY-ALGORITHM-C++-WAY"方法,但是不需要构造fstream
,只需在不需要实际格式化的情况下创建裸的std::filebuf
实例。
- 对于性能要求高的情况,无法超越POSIX文件描述符。虽然不太美观,但在任何平台上都能实现可移植且快速。
- Linux方法似乎非常快速——也许操作系统在I/O完成之前就让函数返回了?无论如何,这对于许多应用程序来说不够可移植。
- “EDIT”:啊,“native Linux”可能通过异步I/O交织读写来提高性能。让命令堆积可以帮助磁盘驱动程序决定何时最好进行寻道。可以尝试使用Boost Asio或pthreads进行比较。至于“无法超越POSIX文件描述符”……好吧,如果你对数据进行任何操作,而不仅仅是盲目复制,那是正确的。
- ANSI C:但是我必须为fread/fwrite函数指定一个大小吗?是的,BUFSIZ确实是一个很好的值,相对于每次只传输一个或“仅有一些”字符,它可能会提高速度。无论如何,性能测试表明,这在任何情况下都不是最佳方法。
- 我对这个问题没有深入的了解,所以在做出假设和发表意见时应该谨慎。据我所知,Linux-Way在内核空间运行。这应该避免内核空间和用户空间之间的慢速上下文切换。明天我会再次查看sendfile的man页。一段时间前,Linus Torvalds说他不喜欢用于繁重任务的用户空间文件系统。也许sendfile是他观点的一个积极例子?
- "sendfile()
在文件描述符之间复制数据。由于这种复制是在内核中进行的,所以sendfile()
比read(2)
和write(2)
的组合更高效,因为后者需要在用户空间和内核空间之间传输数据。":kernel.org/doc/man-pages/online/pages/man2/sendfile.2.html
- 你能贴出使用原始filebuf
对象的示例吗?
在C++17中,复制文件的标准方法是通过包含`
为什么没有一个带有默认参数的单一函数,比如`bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);`?对此我不太确定。也许这并不重要。
在标准库中,干净的代码至关重要。通过提供多个重载函数(而不是一个带有默认参数的函数),可以更清晰地表达程序员的意图。
现在可能应该接受这个答案,因为C++17已经可用。
问题:如何以合理、安全和高效的方式复制文件?
原因:在复制文件时,我们希望能够以一种合理、安全和高效的方式进行操作。现有的方法可能不够简洁、易读,或者不够跨平台,因此需要找到更好的解决方案。
解决方法:根据讨论和建议,可以采用以下几种方式来解决这个问题:
1. 使用C++的流操作进行文件复制。这种方法简单直观,但可能不适用于大量文件复制的情况。在这种情况下,最好使用操作系统调用来访问文件系统。
2. 使用boost库中的文件系统类的copy_file方法进行文件复制。这种方法可能是最具可移植性的方式,可以在不同的操作系统上使用。
3. 使用C标准库中的copyfile函数进行文件复制。这是一种与文件系统交互的C方法,但可能不够跨平台,只适用于某些特定的操作系统,如Mac OS。
4. 在Windows系统上,可以使用Windows提供的CopyFile函数进行文件复制。这是一种简单的方法,可以保证正确复制文件。
需要注意的是,不同的方法可能在不同的情况下表现不同。在选择方法时,需要考虑到目标平台的兼容性、性能要求以及复制文件所需的其他功能(如复制权限)。根据具体的需求和环境选择最适合的方法。