以理智,安全和有效的方式复制文件

我寻找一个复制文件(二进制或文本)的好方法。 我写了几个样本,每个人都工作。 但是我想听听经验丰富的程序员的意见。

我错过了很好的例子,并寻找一种与C ++一起工作的方式。

ANSI-C-WAY

#include <iostream> #include <cstdio> // fopen, fclose, fread, fwrite, BUFSIZ #include <ctime> using namespace std; int main() { clock_t start, end; start = clock(); // BUFSIZE default is 8192 bytes // BUFSIZE of 1 means one chareter at time // good values should fit to blocksize, like 1024 or 4096 // higher values reduce number of system calls // size_t BUFFER_SIZE = 4096; char buf[BUFSIZ]; size_t size; FILE* source = fopen("from.ogv", "rb"); FILE* dest = fopen("to.ogv", "wb"); // clean and more secure // feof(FILE* stream) returns non-zero if the end of file indicator for stream is set 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

POSIX-WAY (K&R在“C编程语言”中使用这个,更低级)

 #include <iostream> #include <fcntl.h> // open #include <unistd.h> // read, write, close #include <cstdio> // BUFSIZ #include <ctime> using namespace std; int main() { clock_t start, end; start = clock(); // BUFSIZE defaults to 8192 // BUFSIZE of 1 means one chareter at time // good values should fit to blocksize, like 1024 or 4096 // higher values reduce number of system calls // 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

KISS-C ++ – Streambuffer-WAY

 #include <iostream> #include <fstream> #include <ctime> 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

COPY-算法-C ++ – WAY

 #include <iostream> #include <fstream> #include <ctime> #include <algorithm> #include <iterator> 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<char> begin_source(source); istreambuf_iterator<char> end_source; ostreambuf_iterator<char> begin_dest(dest); 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

OWN-BUFFER-C ++ – WAY

 #include <iostream> #include <fstream> #include <ctime> using namespace std; int main() { clock_t start, end; start = clock(); ifstream source("from.ogv", ios::binary); ofstream dest("to.ogv", ios::binary); // file size source.seekg(0, ios::end); ifstream::pos_type size = source.tellg(); source.seekg(0); // allocate memory for buffer char* buffer = new char[size]; // copy file source.read(buffer, size); dest.write(buffer, size); // clean up 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; return 0; } 

LINUX-WAY //需要kernel> = 2.6.33

 #include <iostream> #include <sys/sendfile.h> // sendfile #include <fcntl.h> // open #include <unistd.h> // close #include <sys/stat.h> // fstat #include <sys/types.h> // fstat #include <ctime> 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); // struct required, rationale: function stat() exists also 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<double>(end - start) / CLOCKS_PER_SEC << "\n"; 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 # kernel and filesystem buffers are in regular 3. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file 4. $ sha256sum *.ogv # checksum 5. $ rm to.ogg # remove copy, but no sync, kernel and fileystem buffers are used 6. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file 

结果(使用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()的测量中。 他们在源代码中没有其他重大变化。 结果没有改变! 我也用时间来检查我的结果。

更新2
ANSI C示例已更改: while循环的条件不再调用feof(),而是将fread()移到条件中。 它看起来像现在代码运行速度快10000个时钟。

测量更改:以前的结果总是被缓冲,因为我重复了几次每个程序的旧命令行rm to.ogv && sync && time ./program 。 现在我重新启动每个程序的系统。 无缓冲的结果是新的,并不意外。 没有缓冲的结果并没有真正改变。

如果我不删除旧的副本,程序反应不同。 使用POSIX和SENDFILE覆盖缓冲的现有文件速度更快,所有其他程序速度更慢。 也许截断创建的选项会对此行为产生影响。 但是使用相同的副本覆盖现有的文件不是真实世界的用例。

cp执行复制需要0.44秒无缓冲和0.30秒缓冲。 所以cp比POSIX样本慢一点。 对我来说看起来不错。

也许我也从boost :: filesystem添加样本和mmap()copy_file()结果。

更新3
我已经把这个也放在一个博客页面,并扩展了一点点。 包括splice() ,这是Linux内核的一个低级函数。 也许更多的样本与Java将遵循。 http://www.ttyhoney.com/blog/?page_id=69

以合理的方式复制文件:

 int main() { std::ifstream src("from.ogv", std::ios::binary); std::ofstream dst("to.ogv", std::ios::binary); dst << src.rdbuf(); } 

这是如此简单和直观的阅读是值得的额外成本。 如果我们做了很多事情,最好回到操作系统对文件系统的调用。 我确信boost在其文件系统类中有一个复制文件方法。

有一个与文件系统交互的C方法:

 #include <copyfile.h> int copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags); 

使用C ++ 17,复制文件的标准方式将包括<filesystem>标题,并使用:

 bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to); bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options); 

第一种形式与copy_options::none作为选项的第二种形式相同(另请参阅copy_options::none )。

filesystem库最初是作为boost.filesystem开发的,最终从C ++ 17开始合并为ISO C ++。

太多!

由于FILE已被缓冲,所以“ANSI C”方式的缓冲区是多余的。 (这个内部缓冲区的大小是BUFSIZ实际定义的。)

“OWN-BUFFER-C ++ -WAY”将会很慢,因为fstream会执行大量的虚拟调度,并且会再次维护内部缓冲区或每个流对象。 (“COPY-ALGORITHM-C ++ – WAY”不会遇到这种情况,因为streambuf_iterator类绕过了流层。

我更喜欢“COPY-ALGORITHM-C ++ -WAY”,但是如果没有构建fstream ,只需要创建裸std::filebuf实例,而不需要实际的格式化。

对于原始性能,您无法击败POSIX文件描述符。 这是丑陋的,但任何平台上的便携和快速。

Linux的方式似乎是非常快 – 也许操作系统让I / O完成之前函数返回? 无论如何,这对许多应用程序来说都不够便携。

编辑 :啊,“本地Linux”可能会通过交错读取和写入与异步I / O提高性能。 让命令堆积起来可以帮助磁盘驱动程序决定何时最好查找。 您可以尝试Boost Asio或pthreads进行比较。 至于“不能击败POSIX文件描述符”……如果你对数据做任何事情,那么这是真的,而不是盲目的复制。

我想做一个非常重要的注意事项,即使用sendfile()的LINUX方法有一个主要的问题,就是它不能复制大小超过2GB的文件! 我已经实现了这个问题,并遇到了问题,因为我用它来复制大小为GB的HDF5文件。

http://man7.org/linux/man-pages/man2/sendfile.2.html

sendfile()将传输最多0x7ffff000(2,147,479,552)字节,返回实际传输的字节数。 (这在32位和64位系统上都是如此)。

Qt有一个复制文件的方法:

 #include <QFile> QFile::copy("originalFile.example","copiedFile.example"); 

请注意,要使用这个,你必须安装Qt (说明在这里 )并将其包含在你的项目中(如果你使用的是Windows而你不是管理员,那么你可以在这里下载Qt)。 也看到这个答案 。