当前位置: 首页 > 知识库问答 >
问题:

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

郎鸿雪
2023-03-14

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

我错过了好的例子,并搜索了一种与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方法(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;
}

复制-算法-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++-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);

    // 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//需要kernel>=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 80 GB,最多填充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打印相同的结果。
视频文件仍可播放。

问题

>

  • 您喜欢什么方法?
  • 您知道更好的解决方案吗?
  • 您看到我的代码中有错误吗?
  • 你知道避免解决方案的原因吗?

    FSTREAM(KISS,Streambuffer)
    我真的很喜欢这个,因为它真的很短很简单。据我所知,操作符<<对于rdbuf()是重载的,并且不转换任何内容。正确?

    谢谢

    更新1
    我以这种方式更改了所有示例中的源,即文件描述符的打开和关闭包含在clock()的度量中。它们在源代码中没有其他重大变化。结果没变!我还用时间仔细检查了一下我的结果。

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

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

    如果我不删除旧版本,程序的反应就会不同。用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/?page_id=69

  • 共有2个答案

    储毅
    2023-03-14

    在C++17中,复制文件的标准方法是包含 头,并使用:

    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++。

    杨昊
    2023-03-14

    以正常的方式复制文件:

    #include <fstream>
    
    int main()
    {
        std::ifstream  src("from.ogv", std::ios::binary);
        std::ofstream  dst("to.ogv",   std::ios::binary);
    
        dst << src.rdbuf();
    }
    

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

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

    #include <copyfile.h>
    
    int
    copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
    
     类似资料:
    • 下面我分享了我的代码,我试图使用线程安全的Nashorn作为脚本引擎来评估简单的数学公式。公式将类似于“a*b/2”,其中a 我需要知道这种方法是否有助于使Nashorn线程在这个用例中安全。我在Attila的回答中读到,我们可以在线程之间共享脚本引擎对象,因为它们是线程安全的。 对于bindings和eval,因为我们正在为每次执行evaluate创建新线程,每个线程都有自己的bindings对

    • 问题内容: 我正在使用Scikit-learn在数据集上应用机器学习算法。有时我需要具有标签/类的概率,而不是标签/类本身的概率。我不希望将垃圾邮件/非垃圾邮件作为电子邮件的标签,而仅希望举例说明:给定电子邮件为垃圾邮件的概率为0.78。 为此,我使用RandomForestClassifier如下: 我得到了那些结果: 第二列用于分类:垃圾邮件。但是,我对结果有两个主要问题,对此我不确定。第一个

    • 问题内容: 我在与OS无关的文件管理器中工作,并且正在寻找为Linux复制文件的最有效方法。Windows有一个内置函数CopyFileEx(),但是据我所知,Linux没有这种标准函数。所以我想我将必须实现自己的。明显的方法是fopen / fread / fwrite,但是是否有更好(更快)的方法呢?我还必须有能力每隔一段时间停止一次,以便可以更新文件进度菜单的“到目前为止”。 问题答案: 不

    • 即;每个可调用方调用progressBarUpdate(): 每个doSomeStuff()都有自己的异常处理,如果发生错误或抛出异常,则返回一个空值。这就是为什么返回类型是List,并且在这种情况下返回null的原因。调用项和它们返回的文件列表之间没有交叉,它们都维护自己的文件列表。 我发现它工作得很好,但偶尔会抛出窗体的InterruptedException: 我修改了代码,使条件nv>=m

    • 假设我们有一个枚举类型。 一般来说,并非的所有值都是的有效值,因为我们可以选择它们之间的关系。是否有一种通用的方法来创建E的随机、有效(在定义中命名,不可分配)值?例如,这将不起作用: 因为: 值范围不能从0开始 值范围不能以std::numeric\U limits::max()结束 值范围可能根本不是范围-我们可以从uE中为E选择离散值,例如{1,3,64,272} 鉴于所有枚举值在编译时都是

    • 我有一个应用程序,它在本地文件系统的一个文件上使用带有QSQLDatabase的QSQLITE驱动程序。我想写一个备份函数来保存数据库的快照。 简单地复制文件似乎是一种显而易见的简单方法,但我不确定什么时候这样做是安全的。 应用程序在定义良好的点修改数据库。每次都会创建、使用并立即销毁一个新的QSqlQuery对象。显式锁定/刷新是一种可接受的解决方案,但qtapi似乎没有公开这一点。 当Qt将数