Недавно у меня возникла задача добавить сжатие данных в программу. Сразу нашлось две свободных библиотеки – zlib и libbzip2 написанные на C. Изучив вопрос глубже, оказалось что писать удобные обертки над функциями на C не нужно, так как в Boost IOStreams все уже написано.
О том как добавить данный функционал и правильно собрать проект я расскажу в данной заметке. Также будет приведен пример кода для сжатия и распаковки файлов и сравнение zlib (алгоритм DEFLATE, методы gzip и zlib) и libbzip2 (алгоритм bzip2) по скорости работы и уровню сжатия тестовых, бинарных и исполняемых файлов.
Я не буду рассматривать интерфейсы данных библиотек, так как Boost предоставляет довольно удобную абстракцию. Суть абстракции в следующем – имеются устройства представляющие собой например файл отрытый для записи, фильтры, например архивирующий и есть возможность создать фильтрующий поток, содержащий устройство и цепочку из произвольных фильтров. Таким образом создав фильтр сжимающий переданные в него данные и соединив его с устройством пишущим в файл мы получим готовое решение для сжатия и записи данных. Соответственно чтобы распаковать сжатый файл, нам нужно создать устройство представляющее открытый для чтения файл, фильтр который будет распаковывать поступающие в него данные и соединить их.
Сразу приведу код, который рассмотрю ниже:
boost::iostreams::filtering_ostreambuf out; out.push(boost::iostreams::bzip2_compressor()); out.push(boost::iostreams::file_sink("data.bin", std::ios::binary)); boost::iostreams::copy(boost::iostreams::file_source("data.bz2", std::ios::binary), out); ... boost::iostreams::filtering_istreambuf in; in.push(boost::iostreams::bzip2_decompressor()); in.push(boost::iostreams::file_source("data.bz2", std::ios::binary)); boost::iostreams::copy(in, boost::iostreams::file_sink("data.bin", std::ios::binary)); |
По сути в этих восьми строках содержится все необходимое для сжатия и распаковки файла. В обоих случаях мы создаем однонаправленный (только на чтение или только на запись) фильтрующий буфер потока. Создаем фильтр осуществляющий сжатие или распаковку, устройство представляющее файл, и соединяем их в буфере. После этого можно используя утилиту copy из Boost Iostreams копировать содержимое другого потока в буфер или наоборот.
Если вместо буфера (filtering_streambuf) использовать поток (filtering_stream), то писать или читать из него можно используя интерфейс потоков C++.
Устройства в Boost Iostream могут быть источниками (Source) или приемниками (Sink). Соответственно одни могут поставлять данные по требованию откуда либо, например из файла, а другие наоборот данные только куда то отправляют. Причем вы можете написать свой приемник или источник (это несложно) и создать свое устройство реализующее необходимую абстракцию. Например socket_source и socket_sink позволяющие работать с сокетами, pipe_source и pipe_sink тоже самое для именованных каналов или вообще null_sink (который кстати в Boost уже есть), которое просто получив данные тут же о них забывает. Причем соединив, скажем socket_source с фильтром boost::iostreams::bzip2_compressor вы получите передачу через сокеты сжатых данных, а дописав еще шифрующий фильтр можно получить передачу зашифрованных сжатых данных.
Сборка
Чтобы собрать ваш проект с Boost и одной из библиотек сжатия нужно сделать три вещи:
- Собрать библиотеку zlib или bzip2/libzip2 и убедиться, что ваша система сборки увидит путь к заголовочным файлам и собранным библиотекам. Кстати на большинстве Unix систем об этом можно не задумываться, так как скорее всего библиотека уже установлена.
- Установить при сборке проекта переменную компиляции BOOST_IOSTREAMS_NO_LIB
- Включить в сборку один из файлов из Boost (в версии 1.41.0 они находятся в libs/iostreams/src) в зависимости от используемого метода:
- zlib – zlib.cpp
- gzip – gzip.cpp
- bzip2 – bzip2.cpp
Сравнение методов сжатия zlib/gzip/bzip2
Увлекшись я написал небольшой тест для сравнения описываемых методов. Вот результаты запуска программы:
Бинарный файл со случайными числами
Размер: 5 242 880 байт
Метод | Время сжатия, секунд | Время распаковки, секунд | Размер сжатого файла, байт |
zlib | 0.20 | 0.15 | 5 053 306 |
gzip | 0.18 | 0.15 | 5 053 318 |
bzip2 | 0.59 | 0.77 | 5 049 795 |
Текстовой файл (несколько мануалов Linux слитые в один файл)
Размер: 3 534 333 байт
Метод | Время сжатия, секунд | Время распаковки, секунд | Размер сжатого файла, байт |
zlib | 0.28 | 0.07 | 1 050 711 |
gzip | 0.36 | 0.08 | 1 050 723 |
bzip2 | 0.43 | 0.24 | 856 572 |
Исполняемый файл
Размер: 4 568 332 байт
Метод | Время сжатия, секунд | Время распаковки, секунд | Размер сжатого файла, байт |
zlib | 1.51 | 0.10 | 1 636 939 |
gzip | 1.61 | 0.10 | 1 636 951 |
bzip2 | 0.87 | 0.36 | 1 439 625 |
Тест конечно не претендует на серьезное исследование, но определенные выводы сделать позволяет:
- bzip2 сжимает лучше, но при этом значительно медленнее.
- zlib и gzip практически одинаковы. Это правда так, так как они используют один алгоритм, единственное отличие в том, что zlib оптимизирован для сжатия потоковых данных, например из-за этой особенности он используется в протоколе HTTP/1.1, в то время как gz применяется для сжатия файлов.
Файлы к заметке
По традиции вы можете скачать исходный код тестового проекта. Архив содержит код библиотек и файлы для сборки с CMake, таким образом вам нужно только иметь установленный Boost, Cmake и положить в директорию data проекта свои тестовые данные.