Вопрос по file-io, c++ – Скопируйте файл разумным, безопасным и эффективным способом

275

Я ищу хороший способ скопировать файл (двоичный или текстовый). Я написал несколько образцов, каждый работает. Но я хочу услышать мнение опытных программистов.

Я упустил хорошие примеры и ищу способ, который работает с 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 & amp; 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++-WAY

#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 // требуется ядро & gt; = 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;
}

Environment

GNU/LINUX (Archlinux) Kernel 3.3 GLIBC-2.15, LIBSTDC++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16 Using RUNLEVEL 3 (Multiuser, Network, Terminal, no GUI) INTEL SSD-Postville 80 GB, filled up to 50% Copy a 270 MB OGG-VIDEO-FILE

Steps to reproduce

 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

Results (CPU TIME used)

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 выводит те же результаты.
Видео файл все еще воспроизводится.

Questions

What method would you prefer? Do you know better solutions? Do you see any mistakes in my code?

Do you know a reason to avoid a solution?

FSTREAM (KISS, Streambuffer)
I really like this one, because it is really short and simple. As far is I know the operator << is overloaded for rdbuf() and doesn't convert anything. Correct?

Спасибо

Update 1
Я изменил источник во всех примерах таким образом, чтобы открывающие и закрывающие файловые дескрипторы включались в измерениеclock(), Других существенных изменений в исходном коде нет. Результаты не изменились! Я также использовалtime перепроверить мои результаты.

Update 2
Образец ANSI C изменен: состояниеwhile-loop больше не звонитfeof() вместо этого я переехалfread() в состояние. Похоже, код теперь работает на 10 000 часов быстрее.

Измерение изменилось: прежние результаты всегда буферизировались, потому что я повторил старую командную строкуrm to.ogv && sync && time ./program для каждой программы несколько раз. Теперь я перезагружаю систему для каждой программы. Небуферизованные результаты являются новыми и не вызывают удивления. Небуферизованные результаты не изменились.

Если я не удаляю старую копию, программы реагируют по-разному. Перезаписать существующий файлbuffered быстрее с POSIX и SENDFILE, все другие программы медленнее. Возможно вариантыtruncate или жеcreate оказать влияние на это поведение. Но перезапись существующих файлов одной и той же копией не является реальным вариантом использования.

Выполнение копии сcp занимает 0,44 секунды без буферизации и 0,30 секунды с буферизацией. Такcp немного медленнее, чем образец POSIX. Выглядит хорошо для меня.

Может быть, я добавлю также образцы и результатыmmap() а такжеcopy_file() от boost :: filesystem.

Update 3
Я также разместил это на странице блога и немного расширил. В том числеsplice(), которая является функцией низкого уровня из ядра Linux. Может быть, последует больше примеров с Java. http://www.ttyhoney.com/blog/?page_id=69

richelbilderbeek.nl/CppCopy_file.htm Anycorn
Извините за внесение так поздно, но я бы не охарактеризовал ни один из них как «безопасный», так как они не имеют никакой обработки ошибок. Richard Kettlewell
Вы забыли ленивый путь: system (& quot; cp from.ogv to.ogv & quot;); fbafelipe
fstream безусловно, хороший вариант для файловых операций. chris
#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags); Martin York

Ваш Ответ

6   ответов
44

<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 и, наконец, объединены с ISO C ++ с C ++ 17.

Error: User Rate Limit Exceededdoesn't really matter.
Error: User Rate Limit Exceededbool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);?
2

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

Обратите внимание, что для использования этого вам необходимоустановить Qt (инструкцииВот) и включите его в свой проект (если вы используете Windows и не являетесь администратором, вы можете скачать QtВот вместо). Также смэтот ответ.

QFile::copyError: User Rate Limit Exceeded4k buffering.
Error: User Rate Limit ExceededQtError: User Rate Limit Exceeded5.9.2Error: User Rate Limit Exceeded
12

very Важно отметить, что метод LINUX, использующий sendfile (), имеет большую проблему в том, что он не может копировать файлы размером более 2 ГБ! Я реализовал его после этого вопроса и столкнулся с проблемами, потому что использовал его для копирования файлов 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.)

sendfile64 has the same issueError: User Rate Limit Exceededoff64_tError: User Rate Limit Exceededin an answerError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
224

#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 для взаимодействия с файловой системой:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededcodereview.stackexchange.com/q/540/507
Error: User Rate Limit Exceeded
copyfileError: User Rate Limit Exceededboost::filesystem::copy_fileError: User Rate Limit Exceeded
1

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 также доступен какwpath для Юникода. И что вы могли бы также использовать

using namespace boost::filesystem

если вам не нравятся эти длинные имена

Error: User Rate Limit Exceeded
17

«ANSI C»; путь буфера избыточен, так какFILE уже буферизован. (Размер этого внутреннего буфераBUFSIZ на самом деле определяет.)

& Quot; OWN-BUFFER-C ++ - WAY & quot; будет медленным, как он проходитfstream, который выполняет много виртуальной диспетчеризации и снова поддерживает внутренние буферы или каждый объект потока. («COPY-ALGORITHM-C ++ - WAY» не страдает этим, так какstreambuf_iterator класс обходит слой потока.)

Я предпочитаю «COPY-ALGORITHM-C ++ - WAY», но без созданияfstreamПросто создай голоеstd::filebuf случаи, когда фактическое форматирование не требуется.

Что касается производительности, вы не можете превзойти файловые дескрипторы POSIX. Это некрасиво, но портативно и быстро на любой платформе.

Linux выглядит невероятно быстрым & # x2014; возможно ОС позволила функции вернуться до завершения ввода / вывода? В любом случае это недостаточно портативно для многих приложений.

EDITАх, "родной Linux" может повысить производительность за счет чередования операций чтения и записи с асинхронным вводом-выводом. Скопление команд может помочь драйверу диска решить, когда лучше искать. Вы можете попробовать Boost Asio или pthreads для сравнения. Что касается "не может превзойти дескрипторы файлов POSIX" & # x2026; хорошо, что это правда, если вы что-то делаете с данными, а не просто слепо копируете.

Error: User Rate Limit ExceededfilebufError: User Rate Limit Exceeded
Error: User Rate Limit Exceededsendfile()Error: User Rate Limit Exceededsendfile()Error: User Rate Limit Exceededread(2)Error: User Rate Limit Exceededwrite(2)Error: User Rate Limit Exceededkernel.org/doc/man-pages/online/pages/man2/sendfile.2.html
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededpubs.opengroup.org/onlinepubs/9699919799/toc.htm Peter
Error: User Rate Limit Exceeded Peter

Похожие вопросы