复制文件在一个理智,安全和有效的方式

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

我错过了好的例子,并寻找一种与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-ALGORITHM-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;
}

OWN-BUFFER-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 (Archlinux)
  • 内核3.3
  • GLIBC-2.15, libstdc++ 4.7 (GCC- libs), GCC 4.7, Coreutils 8.16
  • 使用RUNLEVEL 3(多用户,网络,终端,无GUI)
  • INTEL SSD-Postville 80gb,最多可填充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输出相同的结果 视频文件仍然可以播放。< / p >

问题

  • 你喜欢什么方法?
  • 你知道更好的解决方案吗?
  • 你在我的代码中发现什么错误了吗?
  • 你知道回避解决方案的理由吗?

  • FSTREAM (KISS, Streambuffer)
    我真的很喜欢这个,因为它真的很短很简单。据我所知,接线员<<对于rdbuf()是重载的,并且不转换任何东西。正确吗?李< / p > < / >

谢谢

< p > 更新1 < br > 我以这种方式改变了所有样本中的源,文件描述符的打开和关闭都包括在钟()的测量中。源代码中没有其他重大更改。结果没有改变!我还使用时间来再次检查结果。< / p > < p > 更新2 < br > while循环的条件不再调用feof (),而是将从文件中读()移动到条件中。看起来,代码运行速度快了10000个时钟。< / p >

度量改变了:以前的结果总是被缓冲,因为我为每个程序重复了几次旧的命令行rm。ogv,,同步,,时间。/项目。现在我为每个程序重新启动系统。未缓冲的结果是新的,并不令人惊讶。没有缓冲的结果并没有什么变化。

如果我不删除旧的副本,程序的反应不同。使用POSIX和SENDFILE重写现有文件缓冲更快,所有其他程序都较慢。也许选项截断创建对这种行为有影响。但是用相同的副本覆盖现有文件并不是一个真实的用例。

使用cp执行复制需要0.44秒未缓冲,0.30秒缓冲。所以cp比POSIX样本稍微慢一点。对我来说还好。

也许我还要从boost::filesystem中添加mmap ()copy_file()的示例和结果。

< p > 更新3 < br > 我也把它放在博客页面上,并做了一些扩展。包括接头(),这是一个来自Linux内核的低级函数。也许接下来会有更多的Java示例。 http://www.ttyhoney.com/blog/?page_id=69 < / p >
246476 次浏览

以正常的方式复制文件:

#include <fstream>


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


dst << src.rdbuf();
}

这是如此简单和直观的阅读,值得额外的费用。如果我们经常这样做,最好还是使用OS对文件系统的调用。我确信boost在它的文件系统类中有一个复制文件的方法。

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

#include <copyfile.h>


int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

太多!

"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完成之前返回?在任何情况下,对于许多应用程序来说,这都不够可移植。

编辑:啊,“本机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位系统)

在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_file)。

filesystem库最初是作为boost.filesystem开发的,最终在c++ 17合并到ISO c++。

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

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

注意,要使用这个,你必须安装Qt(指令在这里)并将其包含在你的项目中(如果你使用Windows并且你不是管理员,你可以下载Qt 在这里)。也可以参见这个答案

对于那些喜欢刺激的人:

boost::filesystem::path mySourcePath("foo.bar");
boost::filesystem::path myTargetPath("bar.foo");


// Variant 1: Overwrite existing
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::overwrite_if_exists);


// Variant 2: Fail if exists
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::fail_if_exists);

注意,boost::文件系统:路径也可以作为Unicode的wpath。你也可以用

using namespace boost::filesystem

如果你不喜欢那些长类型名

我不太确定复制文件的“好方法”是什么,但假设“好”意味着“快”,我可以稍微拓宽一下主题。

当前的操作系统长期以来一直在优化以处理普通的文件复制。再聪明的代码也比不上它。在某些测试场景中,复制技术的某些变体可能会被证明更快,但在其他情况下,它们很可能表现得更差。

通常,sendfile函数可能在提交写操作之前返回,从而给人一种比其他函数快的印象。我还没有读过代码,但这肯定是因为它分配了自己的专用缓冲区,用内存交换时间。为什么它不能工作的文件大于2Gb。

只要你处理的是少量的文件,所有事情都发生在不同的缓冲区中(如果你使用iostream,则是c++运行时的第一个缓冲区,操作系统内部的缓冲区,显然在sendfile的情况下是一个文件大小的额外缓冲区)。实际的存储介质只有在足够的数据被移动到值得旋转硬盘的时候才会被访问。

我认为在特定情况下可以稍微提高性能。我能想到的是:

  • 如果你在同一个磁盘上复制一个巨大的文件,使用一个比操作系统更大的缓冲区可能会改善一点(但我们这里谈论的可能是千兆字节)。
  • 如果你想在两个不同的物理目的地复制同一个文件,你可能会更快地一次打开三个文件,而不是依次调用两个copy_file(尽管你几乎不会注意到差异,只要文件适合OS缓存)
  • 如果你正在处理HDD上的大量小文件,你可能想要批量读取它们以减少查找时间(尽管操作系统已经缓存了目录条目以避免疯狂的查找,而且小文件可能会极大地减少磁盘带宽)。

但所有这些都超出了通用文件复制功能的范围。

因此,在我这个经验丰富的程序员看来,c++文件复制应该只使用c++ 17 file_copy专用函数,除非对文件复制发生的上下文有更多的了解,并且可以设计一些聪明的策略来智取操作系统。

在c++ 17及以后的版本中,最简单的方法是:

使用#include <filesystem>copy()方法。复制方法有4个重载。你可以在链接中检查

void copy( const std::filesystem::path& from,


const std::filesystem::path& to );
void copy( const std::filesystem::path& from,
const std::filesystem::path& to,
std::error_code& ec );
    

void copy( const std::filesystem::path& from,


const std::filesystem::path& to,
std::filesystem::copy_options options );
           

void copy( const std::filesystem::path& from,
const std::filesystem::path& to,
std::filesystem::copy_options options,
std::error_code& ec );

使用copy()方法可以复制文件和目录,带有一些选项,如递归,非递归,只复制目录或覆盖或跳过现有文件,等等。你可以在链接中阅读更多关于复制选项的信息

这是一个来自在这里的示例代码,带有一些编辑:

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
 

int main()
{
// create directories. create all directories if not exist.
fs::create_directories("sandbox/dir/subdir");
    

// create file with content 'a'
std::ofstream("sandbox/file1.txt").put('a');
    

// copy file
fs::copy("sandbox/file1.txt", "sandbox/file2.txt");
    

// copy directory (non-recursive)
fs::copy("sandbox/dir", "sandbox/dir2");
    

// copy directory (recursive)
const auto copyOptions = fs::copy_options::update_existing
| fs::copy_options::recursive
;
fs::copy("sandbox", "sandbox_copy", copyOptions);
    

// remove sanbox directory and all sub directories and sub files.
fs::remove_all("sandbox");
}