首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >以正常、安全和有效的方式复制文件

以正常、安全和有效的方式复制文件
EN

Stack Overflow用户
提问于 2012-04-17 16:38:33
回答 8查看 246.3K关注 0票数 336

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

我遗漏了一些好的例子,并搜索了一种与C++一起工作的方法。

ANSI-C-WAY

代码语言:javascript
复制
#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编程语言”中使用,更低级别)

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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++-WAY

代码语言:javascript
复制
#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++-WAY

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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 80 GB,填充率高达50%
  • 复制270 MB的OGG-视频文件

复制的步骤

代码语言:javascript
复制
 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时间)

代码语言:javascript
复制
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()的度量中。它们并不是源代码中的其他重大更改。结果没有改变!我还用时间反复检查我的结果。

更新2

ANSI C示例改变了:while-循环的条件不再调用feof(),而是将fread()移到条件中。看起来,代码现在运行速度快了10,000个时钟。

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

如果我不删除旧版本,程序的反应就不一样了。用POSIX和SENDFILE覆盖现有的缓冲文件更快,所有其他程序都比较慢。可能选项截断或创建会对此行为产生影响。但是用相同的副本覆盖现有文件并不是真实的用例。

使用cp执行副本需要0.44秒未缓冲和0.30秒缓冲。所以cp比POSIX示例慢一点。对我来说很好。

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

更新3

我也把它放到了一个博客页面上,并对其进行了一些扩展。包括splice(),它是Linux内核中的一个低级函数。也许还会有更多的Java示例出现。id=69

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2012-04-17 16:49:38

以理智的方式复制文件:

代码语言:javascript
复制
#include <fstream>

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

    dst << src.rdbuf();
}

这是如此简单和直观的阅读,这是值得的额外成本。如果我们做了很多,最好回到操作系统对文件系统的调用。我确信boost的文件系统类中有一个复制文件方法。

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

代码语言:javascript
复制
#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
票数 291
EN

Stack Overflow用户

发布于 2016-07-27 15:00:09

使用C++17,复制文件的标准方法将包括头,并使用:

代码语言:javascript
复制
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++中。

票数 79
EN

Stack Overflow用户

发布于 2012-04-17 16:52:09

太多了!

"ANSI“方式缓冲区是冗余的,因为FILE已经被缓冲。(这个内部缓冲区的大小是BUFSIZ实际定义的。)

“自己的缓冲区-C++方式”将是缓慢的,因为它通过fstream进行大量的虚拟调度,并再次维护内部缓冲区或每个流对象。(“复制算法-C++-WAY”不受此影响,因为streambuf_iterator类绕过流层。)

我更喜欢“复制算法-C++方式”,但不构建fstream,只需在不需要实际格式化的情况下创建裸露的std::filebuf实例即可。

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

Linux的方式看起来非常快--也许操作系统让函数在I/O完成之前返回?无论如何,对于许多应用程序来说,这是不够可移植的。

编辑:啊,“本地Linux”可能通过将读写与异步I/O交织在一起来提高性能。让命令堆积起来可以帮助磁盘驱动程序决定什么时候是最好的选择。您可以尝试使用Boost Asio或p线程进行比较。至于“无法击败POSIX文件描述符”,…嗯,如果你对数据做了什么,而不仅仅是盲目地复制,那就是真的。

票数 21
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10195343

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档