我在寻找一个复制文件(二进制文件或文本文件)的好方法。我已经写了几个例子,每个人都工作。但我想听听经验丰富的程序员的意见。
我错过了很好的例子,并寻找一种方法,与C++的工作.
- ANSI-C-方式**
#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 ++-流缓冲区-方式**
#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;
}
- 复制算法-C++方式**
#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;
}
- 自有缓冲区-C++方式**
#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**//要求内核〉= 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(大Linux)
- 内核3.3
- GLIBC-2.15、LIBSTDC ++4.7(全球气候变化委员会-LIBS)、海湾合作委员会4.7、核心小组8.16
- 使用RUNLEVEL 3(多用户、网络、终端、无GUI)
- 英特尔固态硬盘-Postville 80 GB,填充率高达50%
- 复制一个270 MB的OGG视频文件
- 重现步骤**
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(吻,流缓冲区)
我真的很喜欢这一个,因为它真的很短很简单。据我所知,运算符〈〈被rdbuf()重载了,并且没有转换任何东西。对吗?
谢谢
- 更新1**
我以这种方式更改了所有示例中的源代码,即文件描述符的打开和关闭包含在 * clock()* 的度量中。源代码中没有其他重大更改。结果没有更改!我还使用 * time * 来复查我的结果。
- 更新2**
ANSI C示例已更改:while-loop * 的条件不再调用 * feof(),而是将 * fread() 移到条件中,看起来代码运行速度快了10,000个时钟。
测量变更:以前的结果总是被缓冲,因为我对每个程序重复了几次旧的命令行 * rm to. ogv && sync && time ./program 。现在我对每个程序重新启动系统。未缓冲的结果是新的,没有显示出任何意外。未缓冲的结果实际上没有改变。
如果我不删除旧的副本,程序的React会不同。用POSIX和SENDFILE覆盖现有的文件 * buffered * 更快,所有其他程序都更慢。也许选项 * truncate * 或 * create * 对这种行为有影响。但是用相同的副本覆盖现有的文件并不是一个真实的用例。
用 * cp * 执行复制需要0.44秒(无缓冲)和0.30秒(缓冲)。所以 * cp * 比POSIX示例慢一点。看起来我没问题。
也许我还添加了boost::filesystem中的 * mmap() 和 * copy_file()
* 的示例和结果。
- 更新3**
我也把它放在了博客页面上,并对它进行了一些扩展。包括 * splice()*,这是Linux内核中的一个低级函数。可能会有更多的Java示例。http://www.ttyhoney.com/blog/?page_id=69
9条答案
按热度按时间bfrts1fy1#
以正常的方式复制文件:
这是如此简单和直观的阅读它是值得的额外成本。如果我们做了很多,最好回到操作系统调用文件系统。我相信
boost
有一个复制文件的方法在其文件系统类。有一种C方法可用于与文件系统交互:
iqxoj9l92#
在C++17中,复制文件的标准方法是包含
<filesystem>
头文件并使用:第一种形式相当于第二种形式,其中
copy_options::none
用作选项(另请参见copy_file
)。filesystem
库最初是作为boost.filesystem
开发的,最后从C17开始合并到ISO C。vmpqdwk33#
太多了!
“ANSI C”路缓冲区是冗余的,因为
FILE
已经被缓冲了。(这个内部缓冲区的大小是BUFSIZ
实际定义的。)“OWN-BUFFER-C++-WAY”在通过
fstream
时会很慢,fstream
执行大量的虚拟调度,并再次维护内部缓冲区或每个流对象。(“COPY-ALGORITHM-C++-WAY”不受此影响,因为streambuf_iterator
类绕过了流层。)我更喜欢“COPY-ALGORITHM-C++-WAY”,但不构造
fstream
,只要在不需要实际格式化时创建裸std::filebuf
示例即可。就原始性能而言,POSIX文件描述符是无可匹敌的,它很丑,但可移植,在任何平台上都很快。
Linux的方式似乎快得令人难以置信--也许操作系统让函数在I/O完成之前返回?无论如何,这对许多应用程序来说都不够便携。
编辑:啊,“native Linux”可能通过交错读写和异步I/O来提高性能。让命令堆积可以帮助磁盘驱动器决定何时最好地寻找。你可以尝试Boost Asio或pthreads来进行比较。至于“无法击败POSIX文件描述符”...如果你对数据做任何事情,而不仅仅是盲目复制,这是真的。
jutyujz04#
我想做一个 * 非常 * 重要的说明,使用sendfile()的LINUX方法有一个主要的问题,那就是它不能复制大小超过2GB的文件!我已经按照这个问题实现了它,并且遇到了问题,因为我用它来复制大小为许多GB的HDF 5文件。
http://man7.org/linux/man-pages/man2/sendfile.2.html
sendfile()最多传输0x 7 ffff 000(2,147,479,552)字节,返回实际传输的字节数(在32位和64位系统上都是如此)。
gv8xihay5#
Qt有一个复制文件的方法:
请注意,要使用它,您必须install Qt(说明here)并将其包含在您的项目中(如果您使用Windows,并且您不是管理员,您可以下载Qt here)。
42fyovps6#
对于喜欢Boost的用户:
注意,boost::filesystem::path对于Unicode也可以作为wpath使用。
如果您不喜欢这些较长类型名称
envsm3lx7#
我不太确定什么是复制文件的“好方法”,但是假设“好”意味着“快”,我可以稍微拓宽一下主题。
当前的操作系统早已被优化来处理一般的文件复制。没有任何聪明的代码能击败它。复制技术的某些变体可能在某些测试场景中被证明更快,但它们在其他情况下很可能会更糟。
通常,
sendfile
函数可能在写操作提交之前返回,因此给人的印象是比其他函数快。我没有读过代码,但可以肯定的是,这是因为它分配了自己的专用缓冲区,以内存换取时间。这也是它无法处理大于2Gb的文件的原因。只要你处理的文件数量很少,所有的事情都发生在不同的缓冲区中(如果你使用
iostream
,C++运行时是第一个,操作系统内部的缓冲区,在sendfile
的情况下显然是一个文件大小的额外缓冲区)。只有当足够的数据被移动到值得旋转硬盘的麻烦时,实际的存储介质才被访问。我想你可以在特定的情况下稍微提高一下表现。在我的脑海里:
copy_file
更快地同时打开这三个文件(尽管你几乎不会注意到差异,只要文件适合操作系统缓存)但所有这些都超出了通用文件复制功能的范围。
因此,以我可以说是经验丰富的程序员的观点,C文件复制应该只使用C17
file_copy
专用函数,除非对文件复制发生的上下文有更多的了解,并且可以设计出一些聪明的策略来智胜操作系统。mm9b1k5b8#
C++17及更高版本中最简单的方法是:
使用
#include <filesystem>
和copy()
方法。复制方法有4个重载。可以在此link中检查与
copy()
方法可以复制文件和目录与一些选项,如递归,非递归,只复制目录或覆盖或跳过现有的文件,等等.你可以阅读更多关于复制选项在这个link这是here的一个示例代码,经过了一些编辑:
cvxl0en29#
从理论上讲,复制文件最有效的方法是使用内存Map,因此复制过程可以完全在内核模式下完成。
如果文件小于2GB,则可以在Unix平台上使用以下代码:
复制一个2GB的文件,所用时间为:
但如果文件大小大于2GB,则无法使用
write()
。我们必须Map目标文件并使用memcpy
来复制文件。由于使用了memcpy
,因此可以看到在用户模式下花费了时间。以下是一个通用版本:
复制一个3.2GB的文件,所用时间为:
下面是一个Unix版本:
复制一个3.2GB的文件,所用时间为:
下面是一个Windows版本: