【问题标题】:Splitting a file into several parts将文件拆分为多个部分
【发布时间】:2014-03-23 06:24:57
【问题描述】:

我正在尝试在 Qt 中制作文件复制应用程序。我需要将源文件拆分为多个文件,以便我的复制功能可以一个一个地复制部分数据并更新QProgressBar。为了准确更新进度,我需要将源文件拆分为其原始大小的 1%。我的方法是不是错了。我在这个主题上找不到太多资源。如何将源文件分成大小相等的几个部分?

【问题讨论】:

    标签: c++ qt split copy


    【解决方案1】:

    以下是这种异步文件复制器的独立草图。有一些不足:

    1. 报错也需要报QFile错误码。

    2. 确实需要一个自定义互斥锁锁类来处理互斥锁 RAII。

    查看代码时,请确保分别考虑实现和接口(其使用)。该界面使其易于使用。该实现的相对复杂性使得易于使用的界面成为可能。

    #include <QApplication>
    #include <QByteArray>
    #include <QProgressDialog>
    #include <QFileDialog>
    #include <QBasicTimer>
    #include <QElapsedTimer>
    #include <QThread>
    #include <QMutex>
    #include <QFile>
    #include <limits>
    
    class FileCopier : public QObject {
       Q_OBJECT
       QMutex mutable m_mutex;
       QByteArray m_buf;
       QBasicTimer m_copy, m_progress;
       QString m_error;
       QFile m_fi, m_fo;
       qint64 m_total, m_done;
       int m_shift;
    
       void close() {
          m_copy.stop();
          m_progress.stop();
          m_fi.close();
          m_fo.close();
          m_mutex.unlock();
       }
       /// Takes the error string from given file and emits an error indication.
       /// Closes the files and stops the copy. Always returns false
       bool error(QFile & f) {
          m_error = f.errorString();
          m_error.append(QStringLiteral("(in %1 file").arg(f.objectName()));
          emit finished(false, m_error);
          close();
          return false;
       }
       void finished() {
          emitProgress();
          emit finished(m_done == m_total, m_error);
          close();
       }
       void emitProgress() {
          emit progressed(m_done, m_total);
          emit hasProgressValue(m_done >> m_shift);
       }
       void timerEvent(QTimerEvent * ev) {
          if (ev->timerId() == m_copy.timerId()) {
             // Do the copy
             qint64 read = m_fi.read(m_buf.data(), m_buf.size());
             if (read == -1) { error(m_fi); return; }
             if (read == 0) return finished();
             qint64 written = m_fo.write(m_buf.constData(), read);
             if (written == -1) { error(m_fo); return; }
             Q_ASSERT(written == read);
             m_done += read;
          }
          else if (ev->timerId() == m_progress.timerId())
             emitProgress();
       }
       Q_INVOKABLE void cancelImpl() {
          if (!m_fi.isOpen()) return;
          m_error = "Canceled";
          finished();
       }
    public:
       explicit FileCopier(QObject * parent = 0) :
          QObject(parent),
          // Copy 64kbytes at a time. On a modern hard drive, we'll copy
          // on the order of 1000 such blocks per second.
          m_buf(65536, Qt::Uninitialized)
       {
          m_fi.setObjectName("source");
          m_fo.setObjectName("destination");
       }
       /// Copies a file to another with progress indication.
       /// Returns false if the files cannot be opened.
       /// This method is thread safe.
       Q_SLOT bool copy(const QString & src, const QString & dst) {
          bool locked = m_mutex.tryLock();
          Q_ASSERT_X(locked, "copy",
                     "Another copy is already in progress");
          m_error.clear();
    
          // Open the files
          m_fi.setFileName(src);
          m_fo.setFileName(dst);
          if (! m_fi.open(QIODevice::ReadOnly)) return error(m_fi);
          if (! m_fo.open(QIODevice::WriteOnly)) return error(m_fo);
          m_total = m_fi.size();
          if (m_total < 0) return error(m_fi);
    
          // File size might not fit into an integer, calculate the number of
          // binary digits to shift it right by. Recall that QProgressBar etc.
          // all use int, not qint64!
          m_shift = 0;
          while ((m_total>>m_shift) >= std::numeric_limits<int>::max()) m_shift++;
          emit hasProgressMaximum(m_total>>m_shift);
    
          m_done = 0;
          m_copy.start(0, this);
          m_progress.start(100, this); // Progress is emitted at 10Hz rate
          return true;
       }
       /// This method is thread safe only when a copy is not in progress.
       QString lastError() const {
          bool locked = m_mutex.tryLock();
          Q_ASSERT_X(locked, "lastError",
                     "A copy is in progress. This method can only be used when"
                     "a copy is done");
          QString error = m_error;
          m_mutex.unlock();
          return error;
       }
       /// Cancels a pending copy operation. No-op if no copy is underway.
       /// This method is thread safe.
       Q_SLOT void cancel() {
          QMetaObject::invokeMethod(this, "cancelImpl");
       }
       /// Signal for progress indication with number of bytes
       Q_SIGNAL void progressed(qint64 done, qint64 total);
       /// Signals for progress that uses abstract integer values
       Q_SIGNAL void hasProgressMaximum(int total);
       Q_SIGNAL void hasProgressValue(int done);
       ///
       Q_SIGNAL void finished(bool ok, const QString & error);
    };
    
    /// A thread that is always destructible: if quits the event loop and waits
    /// for it to finish.
    class Thread : public QThread {
    public:
       ~Thread() { quit(); wait(); }
    };
    
    int main(int argc, char *argv[])
    {
       QApplication a(argc, argv);
       QString src = QFileDialog::getOpenFileName(0, "Source File");
       if (src.isEmpty()) return 1;
       QString dst = QFileDialog::getSaveFileName(0, "Destination File");
       if (dst.isEmpty()) return 1;
    
       QProgressDialog dlg("File Copy Progress", "Cancel", 0, 100);
       Q_ASSERT(!dlg.isModal());
       Thread thread;
       FileCopier copier;
       copier.moveToThread(&thread);
       thread.start();
       dlg.connect(&copier, SIGNAL(hasProgressMaximum(int)),
                   SLOT(setMaximum(int)));
       dlg.connect(&copier, SIGNAL(hasProgressValue(int)),
                   SLOT(setValue(int)));
       copier.connect(&dlg, SIGNAL(canceled()), SLOT(cancel()));
       a.connect(&copier, SIGNAL(finished(bool,QString)), SLOT(quit()));
       // The copy method is thread safe.
       copier.copy(src, dst);
       return a.exec();
    }
    
    #include "main.moc"
    

    【讨论】:

      【解决方案2】:

      通常有进度条的文件复制是这样完成的:

      1. 打开源文件(供读取)
      2. 打开目标文件(用于写入)
      3. 从源文件中读取一个块(64 kB 左右)并将其写入目标文件
      4. 更新进度条
      5. 重复步骤 3. 和 4. 直到源文件结束
      6. 完成 :) 关闭文件

      无需将文件拆分为多个文件。只需按小块(一个块接一个块)处理文件,而不是一次处理整个文件。

      “在复制之前将文件拆分为多个文件”的方法是错误的 - 这种拆分方法与复制一样昂贵,整个操作将花费两倍的时间,并且您还需要在此拆分过程中更新进度条。

      【讨论】:

      • 谢谢!如何编写仅读取和写入源文件块的代码。如果我将使用来自QFile 的静态复制功能,我可以让它只从文件中复制一个块吗?
      • 你不能使用 QFile 复制,因为你无法获得复制进度。无法将 bytesWritten 与它连接起来。有一个qt bugreport建议使用文件复制对话框:bugreports.qt-project.org/browse/QTBUG-5874也许你可以看一下源代码。
      • 我建议完全不要使用 QFile 复制。只需打开文件,按块读取和写入并更新进度条。
      • 荒谬?那么文件是如何复制的呢? :) 顺便提一句。此处讨论了 I/O 的适当块大小:stackoverflow.com/questions/5445341/…
      • @KubaOber:我不明白您是否不同意我建议的“读取块/写入块/更新进度”周期,或者只是我使用的块大小(4kB)。是的,我们正在讨论应用程序中读/写缓冲区的大小(也许我不应该称之为“块”。)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-28
      • 1970-01-01
      • 2020-05-04
      • 2012-11-10
      • 2010-11-02
      • 1970-01-01
      • 2011-05-30
      相关资源
      最近更新 更多