Прочитать файл с диска в Qt предельно просто (без учета обработки ошибок):
1 2 3 4 |
static const QString FILE_NAME = ...; QFile file( FILE_NAME ); file.open( QIODevice::ReadOnly ); QByteArray data = file.readAll(); |
Но этот код работает приемлемо лишь для относительно небольших файлов. А что если размер файла переваливает за пару сотню Мб? В этом случае приведенная выше реализация заметно подвесит графический интерфейс пользователя. Исправить ситуацию нам помогут потоки в Qt.
Планирование
Воспользуемся реализацией потока в стиле QThreadPool на основе задачи QRunnable. Создадим простой класс LargeFileReaderTask, который умеет:
- Выполнять последовательное чтение файла блок за блоком с помощью QFile;
- Сообщать о ходе выполнения загрузки (сколько получено байтов от общего числа);
- Обеспечить возможность отмены чтения файла;
- Возвращать окончательный результат (прочитанные данные файла).
Реализация
Начинаем с интерфейса класса LargeFileReaderTask. Файл largefilereadertask.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#ifndef LARGEFILEREADERTASK_H #define LARGEFILEREADERTASK_H #include <QObject> #include <QRunnable> #include <QFile> #include <QAtomicInt> static const quint64 DEFAULT_CHUNK_SIZE_KB = 10000; static const quint64 MIN_CHUNK_SIZE_KB = 1000; class LargeFileReaderTask : public QObject, public QRunnable { Q_OBJECT public: enum ResultCode { RESULT_OK, RESULT_FAILED, RESULT_CANCELLED }; public: LargeFileReaderTask( const QString& fileName, quint64 chunkSizeKb = DEFAULT_CHUNK_SIZE_KB ); ~LargeFileReaderTask(); void run(); public slots: void cancel(); signals: void progressChanged( int doneCount, int sumCount ); void readingFinished( int resultCode, const QByteArray& data = QByteArray() ); private: QFile m_file; quint64 m_chunkSizeKb; QAtomicInt m_cancelledMarker; }; #endif // LARGEFILEREADERTASK_H |
Входными данными задачи являются имя файла и размер блока, который может быть прочитан за один шаг. Чем размер блока для чтения меньше, тем быстрее мы можем среагировать на запрос пользователя об остановке. Однако это добавляет накладные расходы и увеличивает суммарное время чтения. В качестве компромисса используем размер в 10 000 Кб.
Обратите внимание на поле m_cancelledMarker типа QAtomicInt. Оно понадобится нам для синхронизации действия отмены чтения. Подробнее об атомарных типах мы поговорим в другой раз.
Теперь реализация. Файл largefilereadertask.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include "largefilereadertask.h" LargeFileReaderTask::LargeFileReaderTask( const QString& fileName, quint64 chunkSizeKb ) : QObject( NULL ), m_file( fileName ), m_chunkSizeKb( chunkSizeKb ), m_cancelledMarker( false ) { if( m_chunkSizeKb < MIN_CHUNK_SIZE_KB ) { m_chunkSizeKb = MIN_CHUNK_SIZE_KB; } } LargeFileReaderTask::~LargeFileReaderTask() { } void LargeFileReaderTask::run() { if( !m_file.open( QIODevice::ReadOnly ) ) { emit readingFinished( RESULT_FAILED ); return; } QByteArray data; QByteArray chunk; do { if( m_cancelledMarker.testAndSetAcquire( true, true ) ) { emit readingFinished( RESULT_CANCELLED ); return; } try { chunk = m_file.read( m_chunkSizeKb * 1024 ); data.append( chunk ); } catch( ... ) { emit readingFinished( RESULT_FAILED ); return; } emit progressChanged( data.size(), m_file.size() ); } while( !chunk.isEmpty() ); if( m_file.error() != QFile::NoError ) { emit readingFinished( RESULT_FAILED ); return; } emit readingFinished( RESULT_OK, data ); } void LargeFileReaderTask::cancel() { m_cancelledMarker.fetchAndStoreAcquire( true ); } |
Само чтение укладывается в функции run(), и заключается в строках:
1 2 |
chunk = m_file.read( m_chunkSizeKb * 1024 ); data.append( chunk ); |
Так мы делаем в цикле до тех пор, пока:
1 |
while( !chunk.isEmpty() ); |
Т.е. пока прочитанный блок не пустой.
Интеграция в GUI-приложение
С помощью LargeFileReaderTask можно создать приложение.
Оно позволяет:
- Выбрать и начать чтение любого файла по кнопке Open;
- Отображать ход выполнения операции чтения с помощью QProgressBar;
- Отменить чтение файла по кнопке Cancel.
Файл largefilereadingdemowidget.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#ifndef LARGEFILEREADINGDEMOWIDGET_H #define LARGEFILEREADINGDEMOWIDGET_H #include <QWidget> namespace Ui { class LargeFileReadingDemoWidget; } class LargeFileReadingDemoWidget : public QWidget { Q_OBJECT public: explicit LargeFileReadingDemoWidget( QWidget* parent = 0 ); ~LargeFileReadingDemoWidget(); signals: void cancelTaskRequired(); private slots: void onOpenFile(); void onProgress( int doneCount, int sumCount ); void onReadingFinished( int resultCode, const QByteArray& data ); private: Ui::LargeFileReadingDemoWidget* ui; }; #endif // LARGEFILEREADINGDEMOWIDGET_H |
И реализация из largefilereadingdemowidget.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#include "largefilereadingdemowidget.h" #include "ui_largefilereadingdemowidget.h" #include <QFileDialog> #include <QThreadPool> #include <QDebug> #include "largefilereadertask.h" LargeFileReadingDemoWidget::LargeFileReadingDemoWidget( QWidget* parent ) : QWidget( parent ), ui( new Ui::LargeFileReadingDemoWidget ) { ui->setupUi( this ); connect( ui->bnOpen, SIGNAL( clicked( bool ) ), SLOT( onOpenFile() ) ); ui->bnCancel->setDisabled( true ); } LargeFileReadingDemoWidget::~LargeFileReadingDemoWidget() { emit cancelTaskRequired(); delete ui; ui = NULL; } void LargeFileReadingDemoWidget::onOpenFile() { QString fileName = QFileDialog::getOpenFileName( this, "Open some large file" ); if( fileName.isEmpty() ) { return; } LargeFileReaderTask* task = new LargeFileReaderTask( fileName ); connect( task, SIGNAL( progressChanged( int, int ) ), SLOT( onProgress( int, int ) ) ); connect( task, SIGNAL( readingFinished( int, QByteArray ) ), SLOT( onReadingFinished( int, QByteArray ) ) ); connect( ui->bnCancel, SIGNAL( clicked( bool ) ), task, SLOT( cancel() ) ); connect( this, SIGNAL( cancelTaskRequired() ), task, SLOT( cancel() ) ); QThreadPool::globalInstance()->start( task ); ui->bnCancel->setEnabled( true ); ui->bnOpen->setDisabled( true ); } void LargeFileReadingDemoWidget::onProgress( int doneCount, int sumCount ) { if( ui ) { ui->prgBar->setMaximum( sumCount ); ui->prgBar->setValue( doneCount ); } } void LargeFileReadingDemoWidget::onReadingFinished( int resultCode, const QByteArray& data ) { Q_UNUSED( data ) if( ui ) { ui->bnCancel->setDisabled( true ); ui->bnOpen->setEnabled( true ); } switch ( resultCode ) { case LargeFileReaderTask::RESULT_OK: { // Успех qDebug() << "OK"; break; } case LargeFileReaderTask::RESULT_FAILED: // Неудача qDebug() << "FAILED"; break; case LargeFileReaderTask::RESULT_CANCELLED: // Чтение отменено qDebug() << "CANCELLED"; break; default: break; } } |
Замечание
Не забывайте, что размер оперативной памяти ограничен. А также имеется ее фрагментация. Поэтому непрерывные блоки больших размеров может быть невозможно уместить в RAM в той или иной ситуации. В связи с этим слишком большие файлы в память грузить не рекомендуется. Лучше обрабатывать их поточными алгоритмами «на лету».
Как сделать такое же, но для записи файлов?