以明智、安全和高效的方式复制一个文件

17 浏览
0 Comments

以明智、安全和高效的方式复制一个文件

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

我需要好的示例,并且正在寻找一个适用于C++的方法。

ANSI-C-WAY

#include

#include // fopen, fclose, fread, fwrite, BUFSIZ

#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(end - start) / CLOCKS_PER_SEC << "\n";

return 0;

}

POSIX-WAY(K&R在《C程序设计语言》中使用这种方法,更底层)

#include

#include // open

#include // read, write, close

#include // BUFSIZ

#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(end - start) / CLOCKS_PER_SEC << "\n";

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(end - start) / CLOCKS_PER_SEC << "\n";

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 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;

}

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(end - start) / CLOCKS_PER_SEC << "\n";

return 0;

}

LINUX-WAY // 需要内核版本 >= 2.6.33

#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);

// 需要的结构,原因:函数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(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 # 内核和文件系统缓冲区处于常规状态

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

0
0 Comments

在这段内容中,讨论了如何以一种理智、安全和高效的方式复制文件。下面是这个问题的出现原因和解决方法的整理:

- 在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对象的示例吗?

0
0 Comments

在C++17中,复制文件的标准方法是通过包含``头文件,并使用`copy_file`函数。`copy_file`函数有两种形式,第一种形式相当于第二种形式,只是使用了`copy_options::none`作为选项。`filesystem`库最初是作为`boost.filesystem`开发的,最终在C++17中合并到了ISO C++中。

为什么没有一个带有默认参数的单一函数,比如`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已经可用。

0
0 Comments

问题:如何以合理、安全和高效的方式复制文件?

原因:在复制文件时,我们希望能够以一种合理、安全和高效的方式进行操作。现有的方法可能不够简洁、易读,或者不够跨平台,因此需要找到更好的解决方案。

解决方法:根据讨论和建议,可以采用以下几种方式来解决这个问题:

1. 使用C++的流操作进行文件复制。这种方法简单直观,但可能不适用于大量文件复制的情况。在这种情况下,最好使用操作系统调用来访问文件系统。

2. 使用boost库中的文件系统类的copy_file方法进行文件复制。这种方法可能是最具可移植性的方式,可以在不同的操作系统上使用。

3. 使用C标准库中的copyfile函数进行文件复制。这是一种与文件系统交互的C方法,但可能不够跨平台,只适用于某些特定的操作系统,如Mac OS。

4. 在Windows系统上,可以使用Windows提供的CopyFile函数进行文件复制。这是一种简单的方法,可以保证正确复制文件。

需要注意的是,不同的方法可能在不同的情况下表现不同。在选择方法时,需要考虑到目标平台的兼容性、性能要求以及复制文件所需的其他功能(如复制权限)。根据具体的需求和环境选择最适合的方法。

0