Прочитать файл с диска в Qt предельно просто (без учета обработки ошибок):
static const QString FILE_NAME = …;
QFile file( FILE_NAME );
file.open( QIODevice::ReadOnly );
QByteArray data = file.readAll();
Но этот код работает приемлемо лишь для относительно небольших файлов. А что если размер файла переваливает за пару сотню Мб? В этом случае приведенная выше реализация заметно подвесит графический интерфейс пользователя. Исправить ситуацию нам помогут потоки.
Воспользуемся реализацией потока в стиле QThreadPool
на основе задачи QRunnable
. Создадим простой класс LargeFileReaderTask
, который умеет:
QFile
;Начинаем с интерфейса класса LargeFileReaderTask
. Файл largefilereadertask.h
:
#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
:
#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()
, и заключается в строках:
chunk = m_file.read( m_chunkSizeKb * 1024 );
data.append( chunk );
Так мы делаем в цикле до тех пор, пока:
while( !chunk.isEmpty() );
Т.е. пока прочитанный блок не пустой.
С помощью LargeFileReaderTask
можно создать подобное приложение:
Оно позволяет:
Open
;QProgressBar
;Cancel
.Файл largefilereadingdemowidget.h
:
#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
:
#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 в той или иной ситуации. В связи с этим слишком большие файлы в память грузить не рекомендуется. Лучше обрабатывать их поточными алгоритмами "на лету".
Скачать исходники с примером отзывчивого чтения файлов в Qt с помощью QFile и QThreadPool
Anonymous:
Идея интересная. Но оно падает с ошибкой сегментирование при попытке чтения даже небольшого файла в 3.6 гигабайт. На линуске.
Позже посмотрю, почему. А пока, увы.
Здравствуйте. Размер в 3.6 Гб не такой уж и небольшой :)
Для чтения файлов больше пары сотен Мб описанный в статье подход лучше не использовать. Вероятно, проблема связана с принципом работы функции append() из QByteArray. Сначала выделяется непрерывный буфер некоторого размера. Если после append-а размер буфера превышен, то выделяется еще один непрерывный буфер большего размера (скорее всего, в два раза больше предыдущего), в который копируется содержимое старого. В худшем случае при чтении файла в 3.6 Гб в тот или иной может потребоваться до 8 Гб оперативной памяти. Все осложняется условием непрерывности буферов.
Можно резервировать место для буфера заранее (через reserve()). Это решение является в данной ситуации предпочтительным.
Ну. если все пытаться хранить в QByteArray, то там ограничение будет примерно 2 в 31 степени байт. Соответственно, будет крах. Лучше такое обрабатывать, но это все таки учебный материал. :)
А большие файлы в моем понимании > 20 гигов. На таких я сей подход даже пробовать не стал. Хотя мог бы, конечно. Просто по работе такого очень много.
Доброго дня. А есть ли реализация подхода типа виндовой функции CreateFileMapping?
Anonymous:
Доброго дня. А есть ли реализация подхода типа виндовой функции CreateFileMapping?
Здравствуйте. Да, в Qt предусмотрена реализация отображения файлов в память. В Qt 4.x для этого предназначена функция-член QFile::map(), а в Qt 5.x - QFileDevice::map()
Anonymous
Идея интересная. Но оно падает с ошибкой сегментирование при попытке чтения даже небольшого файла в 3.6 гигабайт. На линуске.
Позже посмотрю, почему. А пока, увы.