351 Stimmen

Kopieren einer Datei auf eine vernünftige, sichere und effiziente Weise

Ich suche nach einer guten Möglichkeit, eine Datei (binär oder Text) zu kopieren. Ich habe mehrere Beispiele geschrieben, alle funktionieren. Aber ich möchte die Meinung von erfahrenen Programmierern hören.

Ich vermisse gute Beispiele und suche einen Weg, der mit C++ funktioniert.

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 verwenden dies in "Die Programmiersprache C", mehr auf niedriger Ebene)

#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;
}

KOPIER-ALGORITHMUS-C++-WEISE

#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;
}

EIGENER-PUFFER-C++-WEG

#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 // erfordert 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;
}

Umwelt

  • GNU/LINUX (Archlinux)
  • Kernel 3.3
  • GLIBC-2.15, LIBSTDC++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
  • Verwendung von RUNLEVEL 3 (Mehrbenutzer, Netzwerk, Terminal, keine GUI)
  • INTEL SSD-Postville 80 GB, bis zu 50% gefüllt
  • Kopieren einer 270 MB OGG-VIDEO-DATEI

Schritte zur Reproduktion

 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

Ergebnisse (verbrauchte CPU-Zeit)

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  

Die Dateigröße ändert sich nicht.
sha256sum liefern die gleichen Ergebnisse.
Die Videodatei ist noch abspielbar.

Fragen

  • Welche Methode würden Sie bevorzugen?

  • Kennen Sie bessere Lösungen?

  • Siehst du irgendwelche Fehler in meinem Code?

  • Kennen Sie einen Grund, um eine Lösung zu vermeiden?

  • FSTREAM (KISS, Streambuffer)
    Das gefällt mir sehr gut, weil es wirklich kurz und einfach ist. Soweit ich weiß, ist der Operator << für rdbuf() überladen und konvertiert nichts. Korrekt?

Danke

Aktualisierung 1
Ich habe den Quelltext in allen Beispielen so geändert, dass das Öffnen und Schließen der Dateideskriptoren in die Messung von Uhr() . Es gibt keine weiteren signifikanten Änderungen im Quellcode. Die Ergebnisse haben sich nicht verändert! Ich habe auch Zeit um meine Ergebnisse zu überprüfen.

Aktualisierung 2
ANSI C-Beispiel geändert: Der Zustand der while-Schleife ruft nicht mehr an feof() stattdessen bin ich umgezogen fread() in den Zustand. Es sieht so aus, als ob der Code jetzt 10.000 Takte schneller läuft.

Messung geändert: Die bisherigen Ergebnisse wurden immer gepuffert, weil ich die alte Befehlszeile wiederholt habe rm to.ogv && sync && time ./program für jedes Programm ein paar Mal. Jetzt starte ich das System für jedes Programm neu. Die ungepufferten Ergebnisse sind neu und zeigen keine Überraschungen. Die ungepufferten Ergebnisse haben sich nicht wirklich verändert.

Wenn ich die alte Kopie nicht lösche, reagieren die Programme anders. Überschreiben einer bestehenden Datei gepuffert ist mit POSIX und SENDFILE schneller, alle anderen Programme sind langsamer. Vielleicht sind die Optionen abschneiden. o erstellen. Auswirkungen auf dieses Verhalten haben. Das Überschreiben vorhandener Dateien mit derselben Kopie ist jedoch kein realer Anwendungsfall.

Ausführen der Kopie mit cp benötigt 0,44 Sekunden ungepuffert und 0,30 Sekunden gepuffert. Also cp ist ein wenig langsamer als das POSIX-Beispiel. Für mich sieht es gut aus.

Vielleicht füge ich auch Beispiele und Ergebnisse von mmap() y _copy_file()_ von boost::filesystem.

Aktualisierung 3
Ich habe das auch auf eine Blogseite gestellt und ein wenig erweitert. Inklusive splice() die eine Low-Level-Funktion des Linux-Kernels ist. Vielleicht werden weitere Beispiele mit Java folgen. http://www.ttyhoney.com/blog/?page_id=69

3 Stimmen

#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

296voto

Martin York Punkte 245363

Kopieren Sie eine Datei auf eine vernünftige Weise:

#include <fstream>

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

    dst << src.rdbuf();
}

Es ist so einfach und intuitiv zu lesen, dass es die zusätzlichen Kosten wert ist. Wenn wir das oft machen würden, wäre es besser, auf Betriebssystemaufrufe an das Dateisystem zurückzugreifen. Ich bin sicher boost hat eine Methode zum Kopieren von Dateien in seiner Dateisystemklasse.

Es gibt eine C-Methode, um mit dem Dateisystem zu interagieren:

#include <copyfile.h>

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

4 Stimmen

src.close(); dst.close(); ?

89voto

manlio Punkte 17338

Mit C++17 wird der Standardweg, eine Datei zu kopieren, die Einbeziehung der <filesystem> Kopfzeile und Verwendung:

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);

Die erste Form ist äquivalent zur zweiten Form mit copy_options::none als Optionen verwendet (siehe auch copy_file ).

En filesystem Bibliothek wurde ursprünglich entwickelt als boost.filesystem und schließlich ab C++17 in ISO C++ aufgegangen.

21voto

Potatoswatter Punkte 130562

Zu viele!

Der "ANSI C"-Wegpuffer ist überflüssig, da ein FILE ist bereits gepuffert. (Die Größe dieses internen Puffers ist das, was BUFSIZ tatsächlich definiert).

Der "OWN-BUFFER-C++-WAY" wird langsam sein, da er durch fstream das eine Menge virtueller Dispatching-Aufgaben übernimmt und wiederum interne Puffer für jedes Stream-Objekt verwaltet. (Der "COPY-ALGORITHM-C++-WAY" leidet nicht darunter, da der streambuf_iterator Klasse umgeht die Stream-Schicht).

Ich bevorzuge den "COPY-ALGORITHM-C++-WAY", aber ohne die Konstruktion eines fstream erstellen Sie einfach bloße std::filebuf in Fällen, in denen keine eigentliche Formatierung erforderlich ist.

Wenn es um die reine Leistung geht, sind POSIX-Dateideskriptoren unschlagbar. Sie sind hässlich, aber portabel und auf jeder Plattform schnell.

Der Linux-Weg scheint unglaublich schnell zu sein - vielleicht lässt das Betriebssystem die Funktion zurückkehren, bevor die E/A abgeschlossen ist? Auf jeden Fall ist das für viele Anwendungen nicht portabel genug.

EDITAR : Ah, "natives Linux" kann die Leistung verbessern, indem Lese- und Schreibvorgänge mit asynchroner E/A verschachtelt werden. Die Anhäufung von Befehlen kann dem Festplattentreiber dabei helfen, zu entscheiden, wann die Suche am besten ist. Sie könnten zum Vergleich Boost Asio oder pthreads ausprobieren. Was die "POSIX-Dateideskriptoren" betrifft, so stimmt das, wenn man etwas mit den Daten macht und nicht nur blind kopiert.

19voto

rveale Punkte 76

Ich möchte die muy Bitte beachten Sie, dass die LINUX-Methode mit sendfile() ein großes Problem hat, da sie keine Dateien mit einer Größe von mehr als 2 GB kopieren kann! Ich hatte sie in Anlehnung an diese Frage implementiert und stieß auf Probleme, weil ich sie zum Kopieren von HDF5-Dateien mit einer Größe von mehreren GB verwendete.

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

sendfile() überträgt höchstens 0x7ffff000 (2.147.479.552) Bytes, gibt die Anzahl der tatsächlich übertragenen Bytes zurück. (Dies gilt für sowohl auf 32-Bit- als auch auf 64-Bit-Systemen.)

3voto

Donald Duck Punkte 7498

Qt hat eine Methode zum Kopieren von Dateien:

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

Beachten Sie, dass Sie dazu folgende Voraussetzungen erfüllen müssen Qt installieren (Anweisungen これ ) und fügen Sie es in Ihr Projekt ein (wenn Sie Windows verwenden und kein Administrator sind, können Sie Qt これ stattdessen). Siehe auch diese Antwort .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X