Сериализация данных в Qt

Сериализация данных в Qt

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

Другой вариант заключается в использовании обычных файлов. Довольно распространенным форматом в этом случае выступают текстовые файлы, составленные по определенным правилам. В последнее время для этой цели чаще всего используют XML. Парсеры для XML существуют для всех популярных языков программирования, а его синтаксис настолько прост, что без проблем позволяет редактировать содержимое в простом текстовом редакторе. Более подробно об XML вы можете почитать в другой моей заметке: Паттерн Строитель и XML. Этот формат довольно удобный, но крайне избыточный. К тому же, имеет смысл использовать его лишь тогда, когда вы сохраняете текстовые данные. Конечно, вы можете задействовать кодирование, типа base64, для бинарных данных, но проблема избыточности остается не менее актуальной и в этом случае.

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

РЕКОМЕНДУЕМ: Десять советов по созданию гибкого программного кода

Сериализация базовых типов в файл

Пользоваться QDataStrem совсем не сложно. Все примитивные типы C++ и основные классы Qt уже поддерживаются им. Рассмотрим небольшой пример:

Сначала мы создаем объект файла QFile, для которого определили имя test.bin. Затем определяем поток QDataStream, передавая ему в качестве аргумента конструктора указатель на файл. Далее мы открываем файл в режиме только для записи WriteOnly. После чего отправляем несколько значений в поток stream с помощью оператора << (прямая аналогия с std::cout). Для примера я выбрал целое число, число с плавающей точкой, строку и объект прямоугольника. Полный список поддерживаемых типов гораздо обширнее, поэтому за подробностями обращайтесь к официальной документации. Однако если вы собирается записать какой-то из встроенных типов C++ или Qt, то почти наверняка он уже поддерживается.

Обратите внимание, что после записи в поток, я закрываю файл с помощью close(). Это необходимо для того, чтобы вытолкнуть данные из буфера на жесткий диск. Кроме того, сразу после этого мы вновь открываем файл, но уже в режиме для чтения. Здесь следует заметить, что я проигнорировал возможные ошибки чтения-записи, чтобы не загромождать код, но в реальном приложении это обязательно нужно делать.

Теперь, когда данные уже на диске, мы готовы их считать. Если вы заглянете в test.bin, то не увидите ничего особенно интересного. В HEX-режиме редактора это будет выглядеть примерно следующим образом:

Перед тем, как прочитать содержимое файла, мы должны подготовить набор переменных, которые примут считанные значения. Поэтому мы определили 4 переменных в соответствии с тем, что было записано в файл ранее. Само считывание осуществляется практически так же, как и запись, однако теперь мы используем оператор >> (так же работает std::cin). Вот и все. Данные из файла получены. Теперь мы можем спокойно вывести их на консоль с помощью QDebug и убедиться, что все в порядке. В конце же мы вновь закрываем файл с помощью close(). Однако делать это совсем не обязательно, поскольку QFile работает по принципу RAII, поэтому файл в любом случае будет закрыт, когда произойдет вызов деструктора объекта.

Сериализация в [crayon-6605d825335df680668959-i/]

Аналогичным образом вы можете работать с объектами QByteArray для сериализации данных:

Отличие заключается лишь в том, каким образом вы открываете потоки. Для записи в QByteArray необходимо воспользоваться конструктором QDataStream, который в качестве первого аргумента принимает указатель на байтовый массив, а во втором — режим записи. Сам процесс записи данных в поток полностью аналогичен тому, что вы видели для файлов.

Для чтения из байтового массива нам придется создать новый поток, передав константную ссылку в конструктор. А дальше все уже знакомо: объявляем переменные, считываем значения, выводим результаты на консоль для проверки.

А зачем это может понадобиться? На ум приходит несколько вариантов:

  • возможность записи в базу данных. Вероятно, если вам понадобилось сделать что-то подобное, то архитектура приложения или самой БД оказалась не самой удачной. С другой стороны, бывают случаи, когда имеет смысл обращаться с данными однообразно. Если поле таблицы в БД потенциально может хранить изображения, звукозаписи или pdf-файлы, то есть содержимое не столь однозначно, то хранение пользовательских бинарных данных в этом же поле кажется уже не такой плохой идеей;
  • замена QVariant. Здесь уже все не так однозначно, как в случае с базами данных. Хотя это и может оказаться вполне рабочим решением, реальной необходимости его использования я придумать не могу. Очевидных преимуществ оно не дает, но ситуации бывают разные, поэтому иметь запасные варианты всегда полезно.

РЕКОМЕНДУЕМ: Пять правил оптимизации программ

Сериализация пользовательских типов данных

Конечно же, вся мощь сериализации проявляется в ее расширяемости. Если вы не сможете использовать ее для своих собственных типов данных, то пользы от нее будет не много. Разберем следующий пример:

Сама запись и чтение с помощью QDataStream абсолютно идентична тому, что мы уже видели для базовых типов, поэтому особо останавливаться на этом не будем. Сам класс User, который мы сериализуем, тоже довольно простой. Он имеет всего два поля: имя и возраст. Интерес в этом фрагменте представляют три оператора которые мы определяем для этого класса.

Первым идет оператор <<:

Обратите внимание, что мы объявили его вне класса. То есть это обычная функция. Ее сигнатура и содержание очень напоминает то, что у вас получится, если вы решите обеспечить совместимость класса со стандартным потоком вывода. Этих строк уже достаточно, чтобы запись работала.

Теперь чтение:

По сути это тоже обычная функция, но я сделал ее дружественной ( friend) по отношению к классу User. Здесь это требуется для того, чтобы получить доступ к полям m_name и m_age. Если бы мы предусмотрели соответствующие функции-члены для установки этих значений, то объявление friend нам бы не потребовалось. Однако в этом случае мы были бы вынуждены объявлять временные переменные, а размер функции увеличился бы. Сам же вид функции очень напоминает то, что получится у вас, если вы добавите совместимость класса со стандартным потоком ввода.

Запись и чтение уже готовы. Теперь мы можем использовать класс User совместно с потоками QDataStream. Однако остается вопрос проверки того, что мы считали из файла. Для этого я добавил возможность вывода содержимого класса с помощью QDebug:

В очередной раз никаких откровений. Что-то подобное мы уже видели, не правда ли? Единственное, на что следует обратить внимание: QDebug принимается и возвращается не по ссылке, а по значению.

Вот наше знакомство с основами сериализации в Qt и подходит к концу, однако остается один вопрос. Как проверить, что запись и/или чтение прошли без ошибок? Просто проигнорировать мы это не можем. Поэтому в QDataStream предусмотрена функция-член status(). Она возвращает его текущее состояние и указывает на возникшие проблемы.

Кроме того, обратите внимание на различия в версиях форматов сериализации Qt. Между разными выпусками библиотеки могут быть отличия, которые приведут к неожиданным проблемам и ошибкам. Поэтому если при разработке вашего Qt-приложения вы решите перейти на более свежую версию SDK, то при сериализации вам придется ориентироваться на самую старую, если вы хотите сохранить совместимость. Изменить используемую версию можно с помощью QDataStream::setVersion().

Заключение

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

РЕКОМЕНДУЕМ: Способы повышения продуктивности для программиста

Понравилась статья? Поделиться с друзьями:
Добавить комментарий