Qt прочитать файл в qbytearray

Обновлено: 07.07.2024

Приходилось часто работать с классом QDataStream. В результате накопил некоторый опыт, как правильно его использовать.

Введение


Неоднократно замечал, что в начале работы с классом бытует мнение, что раз класс имеет в своем названии stream, то просто обязан хранить данные. Строчка в помощи QDatastream::QDatastream(QByteArray * a, QIODevice::OpenMode mode)
Constructs a data stream that operates on a byte array, a. The mode describes how the device is to be used.
поначалу мало у кого вызывает опасения. Но если взглянуть под капот, то можно увидеть что, никакие данные непосредственно в QDataStream не записываются. В конструкторе инициализируется класс QBuffer, который является в данном случае оберткой для переданного QByteArray’a. Замысел работы такой: данные хранятся исключительно в QByteArray, а все операции по (де)сериализации проводит класс QDataStream. При чтении данных из потока изменяется лишь указатель на текущий байт, при этом сами данные не теряются. При записи данные для QIODevice::WriteOnly затираются новыми значениями, для QIODevice::Append добавляются в конец. Из этого следует вывод о контроле времени жизни QByteArray.

Чтение-Запись

Запись представлена стандартным оператором << определенный для всех основных типов. Однако, зачастую удобней записывать данные сразу в структуру данных. Следующий пример показывает как перегрузить оператор << для наших целей

Здесь все довольно просто: запись «сложной» структуры разбивается на запись более простых типов. Так же обратите внимание на QDataStream writeRawData(const char* s, int len). Можно было бы в цикле записать значения массивов, но зачем это делать, если есть более элегантный способ.Точно так же перегрузим оператор для чтения:

Здесь все тоже самое, но стоит обратить внимание на функцию QDataStream::setFloatingPointPrecision (FloatingPointPrecision precision). Дело в том, что начиная с версии Qt 4.6, требуется явно указывать точность типа с плавающей точкой. Как можно догадаться SinglePrecision нужен для типов с одинарной точностью, а DoublePrecision для типов с двойной. Для решения этой неприятной ситуации есть два пути: первый это перегрузить << и >> примерно так:

Или же в своем коде указывать перед чтением float и double как их считывать. По умолчанию используется DoublePrecision.
Теперь обратим внимание на QDataStream::skipRawData(int len). Данная функция просто пропускает указанное количество байт, что бывает крайне полезно при выравнивании структур.

Отдельно стоит сказать про порядок записи старшего бита. Метод QDataStream::setByteOrder( ByteOrder bo) устанавливает порядок следования. При ByteOrder::BigEndian запись идет старшим байтом вперед, и именно такой порядок применяется по умолчанию. При ByteOrder::LittleEndian запись идет младшим битом вперед.

(Де)Сериализация классов Qt

Кроме стандартных типов С++ QDataStream позволяет записывать также некоторые классы Qt такие как QList и QVariant. Однако тут скрыта некоторые проблемы связанные с версией Qt. Однако, разработчики позаботились о сериализации классов различных версий. Ответственен за это метод QDataStream::setVersion ( int v ), где v указание версии Qt. Тем не менее, стоит помнить, что при возможности протащить классы через различные версии, будут доступны только те свойства класса, которые есть в актуальной версии библиотеки. Получить версию, с которой работает поток, можно при помощи QDataStream::version (). рассмотрим небольшой пример по записи в файл контейнера QHash.

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

В примере мы создали класс simpleClass и перегрузили для него операторы QDataStream. Замете, что мы сделали операторы дружественными к классу, что бы иметь возможность напрямую обращаться к приватным секциям. Можно было бы городить огород перегружая операторы для класса или писать функции доступа к приватным свойствам, но лично мне решение с friend кажется более элегантным. Дальше все довольно просто: открываем файл на чтение, создаем поток с привязкой к файлу, заполняем QHash и записываем, не забывая при этом указать версию Qt. Порядок для чтение практически ничем не отличается.

Это будет первый из нескольких способов чтения данных из файлов. Я постараюсь постепенно усложнять процесс чтения, но при этом будет повышаться эффективность для конкретных задач.

Создадим стандартное Gui приложение Qt с названием fileReading.
На выходе получим 5 файлов:
fileReading.pro - файл проекта;
mainwindow.h - заголовочный файл главного окна;
mainwindow.cpp - файл исходных кодов главного окна;
main.cpp - main.cpp :);
mainwindow.ui - файл формы главного окна.

Итак, в файле mainwindow.h подключаем QFile для работы с файлами и QDebug дабы выводить считанную информацию в консоль.

Также нам потребуется QByteArray , который нам будет возвращать класс QFile .

Ну и конечно же класс строки, без которого не обходится ни одна GUI программа.

В этом же файле объявим функцию для чтения из файла:

Теперь переходим в mainwindow.cpp. В этом файле определим нашу функцию.


В конструкторе file указывается имя файла, либо полный путь до файла, который необходимо считать, так что предварительно вам будет необходимо его создать.
Надеюсь закомментировал все наглядно.

10 комментариев:

Да, очень хорошо. Спасибо!

мда.. времени, конечно, прошло порядочно, но, может, подскажете?
как реализовать средствами Qt чтение без буфера? нужен аналог вот такого кода:
< FILE *f = fopen(str,"r");
setbuf(f,NULL); >
А Qt, как я понимаю, по-умолчанию буфер включает - как отключить -не могу найти. (

Не совсем понимаю что вы хотите от кода.
Возможно вам поможет QDataStream?

Этот комментарий был удален автором.

Не могу прочесть байты

QFile file("d:\\file.txt");/* выбрали файл, переменая "file" теперь за все отвечает. да два слеша обязательно!)) */
QByteArray n file.read(1);/* читаем в переменную n по одному байту - (1)*/

и вот далее я не могу эту "n" использовать, пусто. Берет как Hex как Char, но не как Int, даже Byte такого типа не нашел (чтобы n.fromByte();)
__________________________

В общем, сам отыскал =) вдруг кому приргодится

Создаем еще строковую, текстовую переменную (там, в "explicit", mainwindow.h)
QString s;

теперь идем в mainwindow.cpp и пишем

QByteArray str = file.read(1).toHex();/* прочитали байт как Hex-число в строку */
n = str.toInt();/* преобразовали его в байт, в целое число. ну, был байт 13, а в хэксе он 0D и вот она из 0D опять в 13 перевела, жаль, что сразу нельзя 13 получить в Int!*/

Еще остался без ответа пост "Но к сожалению не читает кирилические символы."
Тут есть особенность. Текстовый файл может быть не в режиме cp1251, или там koi-8, а в юникоде, т.е. UTF8. Это значит, что теперь все нац.символы кодируются в два байта. Скажем, раньше за русскую букву "ю" отвечал байт 209, а теперь будут отвечать 209 и 142 и читать их следует парой). Если прочесть по-байтно, то первый символ будет считаться управляющим и в тексте его программа не разместит. Либо выскочит абракадабра, либо вопросики, или вообще ничего. Но тут еще препятствие - BOM.
Надо прочитать начало текстового файла и выяснить наличие этого BOM, оy сообщает, что сейчас символы пойдут парами, хотя есть UTF8 и без bom.
Вообще, чтобы так уж не "запариваться", лучше прочесть все в текстовую переменную (если файл небольшой) иkb выполнить что-то вроде:

QTextStream in(&inFile);// откуда читаем
QTextStream out(&outFile);// куда записываем
out.setCodec("UTF-8");// пишем в юникод

Пингвин читает содержимое файла

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

Полное имя файла, дерево файловой системы.

Существует такое понятие, как относительное имя файла. Относительное имя файла не содержит полного пути к нему. Его имя относительно к текущей рабочей директории, например из которой запущена программа, которая работает с файлами.

Если мы хотим обратиться к файлу /text.txt, находясь в директории /etc/, то необходимо писать ../text.txt

Если к файлу в текущей директории, то text.txt или ./text.txt

Обычно, когда говорят об имени файла, то подразумевают ту часть, где опущен полный путь к нему, т.е. просто file.txt. Путь к файлу и полное имя файл понятия взаимозаменяемые.

Более подробную информацию о файлах можно найти в сети.

Обратите внимание, что на сайте имеется очень похожая статья статья про реализацию чтения из файлов на C++, но без использования фреймворка Qt.

Класс QFile наследует класс QIODevice, который для работы с файлами предоставляет методы: открытия и закрытия файлов, для записи и чтения из файла, для создания и удаления файлов.

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

Можно не передавать имя файла в конструктор, а установить его в объекте методом setName().

Часто при работе с файлами требуется узнать, открыт ли файл. Метод QIODevice::isOpen() возвращает значение true, если файл открыт и false в противном случае. А так как QFile унаследован от него, то мы можем проверить, открыт ли файл.

Для закрытия файла нужно вызвать метод QFile::close()

Обратите внимание, что данные сразу не записываются в файл на накопителе, они записываются в буфер в оперативной памяти. После закрытия файла данные из буфера записываются в файл на носителе. Это сделано для того, чтобы не нагружать жесткий диск или любой другой тип накопителя, на котором находится файл. Информацию из буфера в файл можно записать принудительно без закрытия файла, вызвав метод QFile::flush()

Существует очень полезный метод QFile::exists(). Он принимает на вход строку с именем файла и возвращает значение true, если такой файл существует. Существует статический и нестатический методы. Для работы со статическим методом необходимо указать имя файла.

Для работы с нестатическим достаточно просто его вызвать.

Для возможности записи или чтения необходимо открыть файл с указанием флага чтения QIODevice::ReadOnly или записи QIODevice::WriteOnly. Пример открытия файла для записи:

Есть разные способы чтения из фалов и записи. Можно считать или записать всю информацию за один раз, а можно по одному символу или блоками.

Для примера напишем программу, которая считывает из файла блок из первых 10-ти символов, а потом вставляет в другой файл.

Я создал файл filein.txt и внёс в него произвольный текст с помощью текстового редактора. После запуска программы я открыл filein.txt и fileout.txt в текстовом редакторе.

Первые 10 символов

Первые 10 символов

Можно было считать все байты, тогда всё содержимое первого файла копировалось во второй. Для полного считывания строку

Нужно заменить на строку

В результате программа считает все байты в массив block, а после запишет их во второй файл.

Мы можем записывать информацию в файл строками, для этого его нужно открыть в текстовом режиме.

После передать адрес в конструктор нового объекста класса QTextStream.

А далее при помощи оператора << посылать строки в поток записи.

Содержимое fileout.txt после запуска программы

Содержимое файла fileout.txt

Содержимое файла fileout.txt

Запись в конец файла

Предыдущий метод полностью перезаписывал данные в файле, то есть очищал всё его содержимое и записывал новые данные. Перезаписи можно избежать и записывать новые данные в конец файла.

Флаг QIODevice::Append помещает указатель для записи (seek) в конец файла, в итоге входящий поток записывается сразу после имеющейся информации в файле. Пример фрагмента использования:

В примере вместо QIODevice::WriteOnly используется QIODevice::Append. Если сделать такое изменение в предыдущей программе, то после нескольких запусков в файле fileout.txt будет храниться строчка

Text, text, text.Text, text, text.Text, text, text.

Итак, мы рассмотрели основные методы для работы с файлами. Более подробную информацию обо всех методах класса QFile и QIODevice можно найти в официальной документации Qt и в сети.

Для вас это может быть интересно:

QFile и файлы. Чтение и запись строк в файл. : 4 комментария

Помогло разобраться с классом, хорошо изложено! 🙂

Не понимаю, где будут сохраняться эти файлы

Добавить комментарий Отменить ответ

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Темой обсуждения этой главы будут -- чтение и запись файлов, навигация по файловой системе и взаимодействие с внешними приложениями.

Qt предоставляет в ваше распоряжение два замечательных класса: QDataStream и QTextStream, которые значительно упрощают операции чтения-записи файлов. Они берут на себя хлопоты о порядке следования байт и кодировке текста, обеспечивая полную совместимость приложений на разных платформах.

Во многих приложениях необходимо реализовать возможность обхода файловой системы или предоставления сведений о файлах. Классы QDir и QFileInfo возьмут на себя эту "черную" и "неблагодарную" работу.

Иногда возникает необходимость запускать другие программы из нашего приложения. Класс QProcess сможет выполнить это в асинхронном режиме, не "замораживая" интерфейс с пользователем.

Чтобы продемонстрировать основные приемы работы с двоичными данными, мы напишем два класса: Drawing и Gallery. Первый будет хранить основные сведения о картине (имя художника, название и год создания), второй -- список картин.

Начнем с класса Gallery.

Он содержит публичные функции, которые сохраняют и загружают данные. Данные -- это список картин. Каждый элемент списка -- это объект класса Drawing. Приватные функции мы будем рассматривать по мере необходимости.

Ниже приводится исходный текст функции, сохраняющей список картин в двоичном виде:

Сначала мы открываем файл. Затем устанавливаем версию QDataStream. Номер версии определяет способ сохранения различных типов данных. Базовые типы языка C++ всегда сохраняются в неизменном виде.

Далее в файл выводится сигнатура (число), которая идентифицирует файлы галереи. Чтобы обеспечить совместимость с другими платформами, мы приводим MagicNumber к типу Q_UINT32.

Список картин выводится в файл приватной функцией writeToStream(). Нет необходимости явно закрывать файл -- это будет сделано автоматически, когда объект QFile выйдет из области видимости по завершении функции.

Функция ioError() вызывает более универсальную функцию error(): Теперь рассмотрим функцию writeToStream(): Она последовательно проходит по списку картин и сохраняет их одну за другой в поток, который был передан в качестве аргумента. Если бы мы, вместо list<Drawing> использовали определение QValueList<Drawing>, мы могли бы обойтись без цикла, просто записав: Когда QValueList<T> помещается в поток, то каждый элемент списка записывается посредством его собственного оператора "<<". Вывод объекта Drawing осуществляется простой записью трех его переменных-членов: myTitle, myArtist и myYear. Перегруженный оператор operator<<() должен быть объявлен как "дружественный" (friend). В заключение функция возвращает поток. Это общепринятая в языке C++ идиома программирования, которая позволяет объединять операторы "<<" в цепочки, например: Ниже приводится определение класса Drawing: Рассмотрим функцию, которая читает файл со списком картин: Файл открывается на чтение и создается объект QDataStream, который будет читать данные из файла. Мы установили версию 5 для QDataStream, поскольку в этой версии была произведена запись в файл. Использование фиксированного номера версии -- 5, гарантирует, что приложение всегда сможет читать и записывать данные, если оно собрано с Qt 3.2 или более поздней.

Работа с файлом начинается со считывания сигнатуры (числа) MagicNumber. Это дает нам уверенность, что мы работаем с файлом, содержащим список картин, а не что-то иное. Затем список считывается функцией readFromStream().

Функция начинается с очистки ранее находившихся в списке данных. Затем в цикле производится считывание всех описаний картин, одного за другим. Если бы мы, вместо list<Drawing> использовали определение QValueList<Drawing>, мы могли бы обойтись без цикла, просто записав: Когда QValueList<T> получает данные из потока, то каждый элемент списка читается посредством его собственного оператора ">>". Реализация оператора ">>" является зеркальным отражением оператора "<<". При использовании QDataStream у нас не возникает необходимости производить синтаксический анализ в любом его проявлении.

При желании, читать и записывать любые двоичные данные в необработанном виде, можно с помощью функций readRawBytes() и writeRawBytes().

Чтение и запись данных базовых типов (таких как Q_UINT16 или float), может производиться как операторами "<<" и ">>", так и с помощью функций readRawBytes() и writeRawBytes(). По-умолчанию, порядок следования байт, используемый QDataStream -- "big-endian". Для того, чтобы изменить его на "little-endian" (храктерный для платформы Intel), необходимо указывать его явно:

В случае чтения/записи базовых типов языка C++, указывать версию, через вызов setVersion(), необязательно.

Если необходимо записать/прочитать файл, что называется "за один присест", то можно воспользоваться методами класса QFile -- writeBlock() и readAll(), например:

Данные, записанные таким образом, находятся в файле в виде простой последовательности байт. Однако, в этом случае, вся ответственность за структурирование и идентификацию данных при считывании, полностью ложится на плечи разработчика. За создание списка QByteArray и заполнение его данными, в классе Gallery отвечает приватная функция getData(). Чтение блока данных из файла выглядит не менее просто, чем запись: За извлечение данных из QByteArray, в классе Gallery отвечает приватная функция setData().

Сохранение всех данных, в виде QByteArray, может потребовать значительного объема памяти, но такой способ имеет свои преимущества. Например, мы можем сжать данные, с помощью qCompress(), при записи в файл:

И разархивировать при считывании: Ниже приводится один из возможных вариантов реализации функций getData() и setData(): Здесь создается поток QDataStream, которому в качестве устройства вывода, вместо QFile, назначается QByteArray. После этого массив заполняется двоичными данными, вызовом writeToStream().

Аналогичным образом, функция setData() обращается к readFromStream(), для чтения ранее записанных данных:

В примерах выше, мы сохраняли и считывали данные, жестко задавая номер версии для QDataStream. Такой подход достаточно прост и надежен, но он имеет один маленький недостаток: мы не сможем работать с файлами, записанными с новыми версиями. Например, если в последующих версиях Qt, в класс QFont будут добавлены новые элементы, то мы лишимся возможности сохранять и загружать компоненты этого типа, используя более старую версию QDataStream.

Как одно из возможных решений этой проблемы -- записывать в файл номер версии:

Этот код будет выполнять запись данных, с использованием самой последней версии QDataStream.

При чтении таких файлов, сначала будет считываться сигнатура файла и номер версии QDataStream:

Вместо версии QDataStream можно использовать версию приложения. Например, допустим, что некий формат файла соответствует версии 1.3 приложения. Тогда мы могли бы записать следующий код:

При чтении такого файла можно определять версию QDataStream, основываясь на версии приложения: Этот код говорит, что для чтения данных из файла, созданного приложением с версией 1.2 или более ранней, должна использоваться 4-я версия QDataStream, для чтения данных из файла, созданного приложением с версией 1.3 -- 5-я версия QDataStream.

Как только мы получаем в руки механизм определения версии QDataStream, процедура чтения и записи двоичных данных становится простой и надежной.

Читайте также: