关于c ++:以合理,安全,有效的方式复制文件

Copy a file in a sane, safe and efficient way

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

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

ANSI-C-WAY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" << end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

posix-way(k&r在"C编程语言"中使用它,更低级)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" << end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

KISS-C++-流缓冲方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" <<  end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

copy-algorithm-c++-方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <fstream>
#include <ctime>
#include
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" <<  end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

own-buffer-c++-路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" <<  end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

linux-way//需要内核>=2.6.33

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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 <<"
"
;
    cout <<"CPU-TIME START" << start <<"
"
;
    cout <<"CPU-TIME END" << end <<"
"
;
    cout <<"CPU-TIME END - START" <<  end - start <<"
"
;
    cout <<"TIME(SEC)" << static_cast<double>(end - start) / CLOCKS_PER_SEC <<"
"
;

    return 0;
}

环境

  • GNU/Linux(ArchLinux)
  • 内核3.3
  • glibc-2.15,libstdc++4.7(gcc-libs),gcc 4.7,coreutils 8.16
  • 使用运行级别3(多用户、网络、终端、无GUI)
  • Intel SSD Postville 80 GB,已满50%
  • 复制一个270 MB的ogg-video-file

复制的步骤

1
2
3
4
5
6
 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时间)

1
2
3
4
5
6
7
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(吻,streambuffer)我真的很喜欢这个,因为它很短很简单。据我所知,rdbuf()的操作符<<被重载,并且不转换任何内容。对的?

谢谢

更新1我以这种方式更改了所有样本的源代码,即文件描述符的打开和关闭都包含在clock()的度量中。它们不是源代码中的其他重要更改。结果没有改变!我也用时间来复查我的结果。

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

测量结果发生了变化:以前的结果总是得到缓冲,因为我对每个程序重复了几次旧的命令行rm to.ogv&;sync&;time./程序。现在我为每个程序重新启动系统。未缓冲的结果是新的,并不令人惊讶。未缓冲的结果并没有真正改变。

如果我不删除旧的拷贝,程序的反应就不同了。使用posix和sendfile覆盖现有的缓冲文件更快,所有其他程序都较慢。可能选项truncate或create会影响此行为。但是用相同的副本覆盖现有的文件并不是一个真实的用例。

用cp执行复制需要0.44秒的无缓冲时间和0.30秒的缓冲时间。所以cp比posix示例慢一点。我看起来不错。

也许我还添加了来自boost::filesystem的mmap()和copy_file()的示例和结果。

更新3我也把它放在了一个博客页面上,并扩展了一点。包括splice(),它是Linux内核中的一个低级函数。也许会有更多的Java样本。http://www.ttyhoney.com/blog/?PaGyId=69


以正常方式复制文件:

1
2
3
4
5
6
7
8
9
#include <fstream>

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

    dst << src.rdbuf();
}

这是如此简单和直观的阅读它是值得额外的费用。如果我们经常这样做,最好还是回到对文件系统的操作系统调用上。我确信boost在其文件系统类中有一个copy-file方法。

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

1
2
3
4
#include <copyfile.h>

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


用C++ 17,复制文件的标准方法将包括EDCOX1×7×标题和使用:

1
2
3
4
5
6
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)。

EDOCX1×10库最初被开发为EDCOX1×11,最终合并到C++ 17的ISO C++中。


太多了!

"ansi c"方式缓冲区是冗余的,因为已经缓冲了FILE。(此内部缓冲区的大小是BUFSIZ实际定义的。)

当"own-buffer-c++"通过执行大量虚拟调度的fstream时,它将变慢,并再次维护内部缓冲区或每个流对象。(由于streambuf_iterator类绕过流层,"copy-algorithm-c++-way"不受此影响。)

我更喜欢"copy-algorithm-c++-way",但是在不构建fstream的情况下,只需在不需要实际格式化的情况下创建裸露的std::filebuf实例即可。

对于原始性能,不能超过POSIX文件描述符。它很难看,但便携,在任何平台上都很快。

Linux方式看起来非常快——也许操作系统会在I/O完成之前让函数返回?在任何情况下,对于许多应用程序来说,这都是不可移植的。

编辑:啊,"本机Linux"可能通过使用异步I/O交错读写来提高性能。让命令堆积起来可以帮助磁盘驱动程序决定什么时候是最佳搜索方式。您可以尝试增强asio或pthreads进行比较。至于"不能打败posix文件描述符"…如果你对数据做了任何事情,而不仅仅是盲目复制,那是真的。


我要特别指出的是,使用sendfile()的Linux方法存在一个主要问题,即它无法复制大小超过2GB的文件!我按照这个问题实现了它,并且遇到了问题,因为我使用它来复制大小为许多GB的HDF5文件。

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

sendfile() will transfer at most 0x7ffff000 (2,147,479,552) bytes,
returning the number of bytes actually transferred. (This is true on
both 32-bit and 64-bit systems.)


qt有一种复制文件的方法:

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

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


对于喜欢Boost的人:

1
2
3
4
5
6
7
8
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::filesystem::path也可用作unicode的wpath。你也可以用

1
using namespace boost::filesystem

如果你不喜欢那些长字体的名字