发布日期:2018-03-26
用一个健壮的、安全、有效的方法复制文件+ 查看更多
用一个健壮的、安全、有效的方法复制文件
+ 查看更多
发布日期:2018-02-26 15:43
分类:CPlusPlus
浏览次数:202
我在寻找一种复制文件(二进制或文本形式)的好方法。我已经写了几个例子,每一个都有效。但是我想听一听老练的程序员的意见。
我缺少好的例子并且在寻找一种使用C++的有效方法。
ANSI-C方式
#include#include // fopen, fclose, fread, fwrite, BUFSIZ #include 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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
POSIX方式 (K&R 在《C程序设计语言》中用了这种方式,它更加底层)
#includeKISS-C++-Streambuffer方式#include // open #include // read, write, close #include // BUFSIZ #include 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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
#includeCOPY-ALGORITHM-C++方式#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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
#includeOWN-BUFFER-C++方式#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 begin_source(source); istreambuf_iterator end_source; ostreambuf_iterator 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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
#includeLINUX方式 //需要内核版本 >= 2.6.33#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); // 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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
#include环境#include // sendfile #include // open #include // close #include // fstat #include // fstat #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); // 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 (end - start) / CLOCKS_PER_SEC << "\n"; return 0; }
GNU/LINUX (Archlinux) Kernel 3.3 GLIBC-2.15, LIBSTDC++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16 Using RUNLEVEL 3 (Multiuser, Network, Terminal, no GUI) INTEL SSD-Postville 80 GB, filled up to 50% Copy a 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时间)
程序 描述 无缓冲|有缓冲
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
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输出相同结果。
这个视频文件还是可以播放的。
sha256sum输出相同结果。
这个视频文件还是可以播放的。
你更喜欢什么方法? 你知道更好的解决方案吗? 你在我的代码中发现错误了吗? 你知道不使用某一种方案的原因吗? FSTREAM (KISS, Streambuffer) 我真的喜欢这个函数,因为它短而简单。据我所知,操作符<<被rdbuf()重载并且不变换任何东西。对吗?
谢谢
修正 1
我修改了所有例子的源代码,文件描述符的打开和关闭被包含在了clock()的测量范围内。源代码中没有其他的重要修改。结果没有改变!我也使用了时间来复核我的结果。
我修改了所有例子的源代码,文件描述符的打开和关闭被包含在了clock()的测量范围内。源代码中没有其他的重要修改。结果没有改变!我也使用了时间来复核我的结果。
修正 2
ANSI C 版的例子改变了: while循环的条件不再调用feof(), 作为代替,我将fread()放入了条件。这个代码现在似乎执行快了10,000多个时钟周期。
ANSI C 版的例子改变了: while循环的条件不再调用feof(), 作为代替,我将fread()放入了条件。这个代码现在似乎执行快了10,000多个时钟周期。
测量方式改变了:之前的结果总是有缓冲的,因为我数次对每一个程序重复了旧的命令行rm to.ogv && sync && time ./program。现在
我为每一个代码重启系统。无缓冲的结果是新的但没有惊喜。无缓冲的结果确实没有变化。
我为每一个代码重启系统。无缓冲的结果是新的但没有惊喜。无缓冲的结果确实没有变化。
如果我不删除旧副本,代码有不同的反应。用POSIX和SENDFILE重写一个已存在的缓冲文件是更快的。其他代码更慢。也许函数中的参数选择是truncate还是create会对此行为有影响。但是用相同的副本重写一个已存在的文件算不上一个用例。
用cp命令执行复制,无缓冲的话花了0.44秒,而有缓冲花了0.30秒。所以cp比POSIX版的例子慢一些。对我而言看起来不错。
也许我也应该增加使用boost::filesystem中的mmap()和copy_file()的例子和结果。
也许我也应该增加使用boost::filesystem中的mmap()和copy_file()的例子和结果。
修正 3
我也把这个问题放在了一个博客页面上并将它扩展了一些。包含了splice(),它是Linux内核中的一个底层函数。也许更多使用Java的例子将接踵而至。 http://www.ttyhoney.com/blog/?page_id=69
我也把这个问题放在了一个博客页面上并将它扩展了一些。包含了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语言方法:
#includeint copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);