Консольное приложение для сжатия и распаковки файлов и папок с использованием алгоритма Хаффмана.
HaffmanArch — это утилита командной строки, написанная на языке C, предназначенная для эффективного сжатия данных. Она поддерживает работу с отдельными файлами и целыми директориями, а также позволяет выбирать ширину символа для кодирования (1 или 2 байта).
- Сжатие: Уменьшение размера файлов и папок.
- Распаковка: Восстановление оригинальных данных из архива.
- Поддержка переменной ширины символа: Автоматическое определение или ручное указание (1 или 2 байта) для оптимального сжатия различных типов данных.
- Рекурсивная обработка папок: Сжатие и распаковка содержимого папок, включая вложенные структуры.
- Отображение прогресса: Индикация выполнения длительных операций.
- Справочная информация: Встроенная помощь по командам и опциям.
HaffmanArch/
├── src/ # Исходные коды проекта
│ ├── main.c # Главный файл, обработка аргументов, запуск операций
│ ├── huffman.c # Реализация алгоритма Хаффмана
│ ├── huffman.h # Заголовочный файл для huffman.c
│ ├── archive.c # Функции архивации и разархивации
│ ├── archive.h # Заголовочный файл для archive.c
│ ├── bitio.c # Функции побитового ввода/вывода
│ ├── bitio.h # Заголовочный файл для bitio.c
│ ├── fsUtils.c # Утилиты для работы с файловой системой
│ ├── fsUtils.h # Заголовочный файл для fsUtils.c
│ ├── Heap.c # Реализация минимальной кучи
│ ├── Heap.h # Заголовочный файл для Heap.c
│ ├── progress.c # Функции отображения прогресса
│ ├── progress.h # Заголовочный файл для progress.c
│ ├── help.c # Функции вывода справки
│ └── help.h # Заголовочный файл для help.c
├── obj/ # Объектные файлы (генерируются при сборке)
├── Makefile # Файл для сборки проекта
├── README.md # Этот файл
└── huffmatic # Исполняемый файл архиватора (после сборки)
- Компилятор GCC
- Утилита Make
Для сборки проекта выполните команду в корневой директории HaffmanArch:
makeЭта команда скомпилирует исходные файлы и создаст исполняемый файл huffmatic в корневой директории проекта.
./huffmatic <режим> [опции] [файлы ...] <архив>
-hили--help: Показать справочную информацию.
-
-cРежим сжатия (упаковки). Укажите один или несколько входных файлов или директорий, а затем имя создаваемого архива. -
-dРежим распаковки (извлечения). Укажите имя архива для распаковки. Можно также указать имена конкретных файлов или директорий из архива для извлечения только их. Если имена не указаны, распаковывается всё содержимое архива.
-s <ширина>Задает ширину символа для кодирования Хаффмана. Если опция не указана, используется ширина символа 1 байт. <ширина> может быть: 1 - для 1-байтных символов. Это значение по умолчанию. 2 - для 2-байтных символов. Пример: ./huffmatic -c -s 2 мой_файл.txt мой_архив.huf
-
Упаковать один файл с настройками по умолчанию:
./huffmatic -c документ.txt архив.huf -
Упаковать несколько файлов и директорию:
./huffmatic -c файл1.txt папка1 файл2.jpg архив.huf -
Упаковать файл, используя 2-байтную ширину символа:
./huffmatic -c -s 2 текст.txt архив.huf -
Распаковать все содержимое архива:
./huffmatic -d архив.huf -
Распаковать только указанные файлы из архива:
./huffmatic -d архив.huf файл1.txt папка/файл2.doc
Название архива не обязательно должно заканчиваться на .huf ! Оно может быть любым. При сжатии программа выводит статистику, включая общий размер входных файлов, размер полученного архива и коэффициент сжатия. При распаковке программа автоматически определяет ширину символа, использованную при сжатии, из заголовка архива.
- Анализ частот: Программа подсчитывает частоту каждого символа (1-байтового или 2-байтового, в зависимости от режима) во входных данных.
- Построение дерева Хаффмана: На основе таблицы частот строится бинарное дерево Хаффмана, где символы с меньшей частотой располагаются дальше от корня.
- Генерация кодов: Для каждого символа генерируется уникальный префиксный код Хаффмана (последовательность битов). Часто встречающиеся символы получают короткие коды, редкие — длинные.
- Запись метаданных: В начало архива записывается служебная информация:
- Сигнатура архива.
- Информация о ширине символа (1 или 2 байта).
- Таблица частот, необходимая для декодирования.
- Информация о структуре файлов и папок (для архивации директорий).
- Кодирование и запись данных: Исходные данные заменяются их кодами Хаффмана и записываются в архив в виде плотного потока битов.
- Чтение метаданных: Из архива считывается служебная информация, включая данные для восстановления дерева Хаффмана и структуру оригинальных файлов/папок.
- Декодирование данных: Архив читается побитово. Каждая последовательность битов, соответствующая коду Хаффмана, заменяется на исходный символ.
- Восстановление файлов: Декодированные данные записываются в файлы, восстанавливая оригинальную структуру, если была сжата папка.
Ниже более подробное описание того, что делает каждый файл вашего архиватора (расширив ваш черновик и добавив недостающие детали).
-
Задачи:
- Точка входа
int main(int argc, char *argv[]). - Разбор и валидация аргументов (режим
-c/-d, флаг ширины символа-s, список входных путей и имя архива). - В случае некорректных аргументов или флага
-h/--helpвызываетprint_usage()изmain.cилиprint_full_help()изhelp.c. - В режиме сжатия вызывает
archive_compress(...)изarchive.c, в режиме распаковки —archive_decompress(...). - Обрабатывает коды возврата и выводит понятные сообщения об ошибках («не корректный архив», «ошибка распаковки» и т. п.).
- Точка входа
-
Функции:
static void print_usage(const char *prog)— краткая подсказка по синтаксису.int main(…)— как описано выше.
-
Задачи: При запросе подробной справки (обычно через
-h/--help) выводит на stdout описание всех режимов, ключей и примеры использования. -
Функции:
void print_full_help(const char *prog_name)— выводит детальное руководство по работе с утилитой.
-
Задачи: Работа с файловой системой при сборе списка файлов для архивации и освобождении ресурсов.
-
Основные возможности:
-
Рекурсивный обход входных путей (файлов и директорий), чтобы собрать все файлы для последующего сжатия.
-
Формирование структуры
FileEntryдля каждого файла:typedef struct FileEntry { char *path; // полный путь на диске char *name; // путь внутри архива (относительный) uint64_t size; // размер файла } FileEntry;
-
Поддержка автоматического расширения массива
FileEntry *при добавлении новых элементов. -
Освобождение памяти при помощи
free_entries().
-
-
Функции:
FileEntry *scan_paths_recursive(int path_count, const char *paths[], const char *base_path_for_name, int *out_count)Рекурсивно сканирует переданныеpaths[], заполняет массивFileEntry, возвращает его и количество.uint64_t get_file_size(const char *path)— обёртка надstat()для полученияst_size.void free_entries(FileEntry *entries, int count)— освобождает все строкиpath,nameи сам массив.
-
Задачи: Битовый ввод/вывод — запись и чтение не целых байт, а отдельных битов, как того требует кодирование Хаффмана.
-
Структура:
typedef struct { FILE *file; uint8_t buffer; // «собираем» или «срезаем» биты здесь int position; // сколько бит уже записано/прочитано в buffer char mode; // 'r' или 'w' } BitIO;
-
Функции:
BitIO *bitio_open(const char *filename, const char *mode)— открыть файл в режиме чтения/записи битов, инициализировать буфер.int bitio_read(BitIO *bitio)— прочитать следующий бит (0 или 1), возвращает EOF при окончании.int bitio_write(BitIO *bitio, int bit)— дописать бит (0/1) в буфер, и при заполнении байта — записать его в файл.int bitio_close(BitIO *bitio)— при записи дозаполняет последний байт нулями, закрывает файл и освобождаетBitIO.
-
Задачи: Стандартный минимальный (min-)куча для узлов дерева Хаффмана; нужен для построения дерева по частотам.
-
Структура:
typedef struct { int size; int capacity; HuffmanNode **array; // массив указателей на узлы } MinHeap;
-
Функции:
MinHeap *createMinHeap(int capacity)— выделяет и возвращает пустую кучу заданной вместимости.void destroyMinHeap(MinHeap *heap)— освобождает память.void insertNode(MinHeap *heap, HuffmanNode *node)— вставка узла с сохранением свойства min-кучи.HuffmanNode *extractMin(MinHeap *heap)— извлечение узла с наименьшей частотой.- Внутренние (static):
swapNodes(),minHeapify()— поддерживают упорядоченность кучи.
-
Задачи: Собственно алгоритм Хаффмана: подсчёт частот символов, построение дерева, генерация кодов, сжатие и распаковка отдельных файлов.
-
Ключевые типы:
typedef struct HuffmanNode { uint64_t freq; uint16_t symbol; struct HuffmanNode *left, *right; } HuffmanNode; typedef struct { uint16_t length; uint32_t bits; } Code;
-
Функции:
static HuffmanNode **make_node_list(const size_t *freq, size_t alphabet_size, int *out_n)Создаёт список узлов для всех символов с ненулевой частотой.HuffmanNode *build_huffman_tree(const size_t *freq, size_t alphabet_size)Строит дерево: заводит все узлы вMinHeap, затем последовательно извлекает два наименьших, объединяет их в новый узел и вставляет обратно.void generate_codes(const HuffmanNode *root, Code *codes, char *buffer, size_t depth, size_t alphabet_size)Обход дерева в глубину, заполняет массивcodes[symbol] = {длина, биты}.void free_huffman_tree(HuffmanNode *root)— освобождает память всего дерева.int huffman_compress(const char *input_filename, const char *output_filename, progress_callback_t callback, int symbol_width)— подсчитывает частоты (читаяsymbol_widthбайт за раз), строит дерево, генерирует коды, записывает в начало выходного файла заголовок кодовой таблицы, затем побитово пишет закодированные данные. Вызываетcallbackдля отображения прогресса.int decode_symbol(BitIO *input, const HuffmanNode *root)— читает биты до встречи листа дерева, возвращает decoded symbol.int huffman_decompress(const char *input_filename, const char *output_filename, progress_callback_t callback, int symbol_width)— читает заголовок (частотные данные или дерево), собирает дерево, побитово читает код и восстанавливает исходные символы в выходной файл.
-
Задачи: Высокоуровневая логика «многопоточности» архива: работа не с одним файлом, а с набором файлов/директорий, организация единого архива, формат заголовка и метаданных, пакетная упаковка и распаковка.
-
Основные шаги компрессии (
archive_compress):-
Сканирование всех входных путей через
scan_paths_recursive()→ массивFileEntry. -
Открытие архива на запись и запись:
- байта
symbol_width(1 или 2), - 4-байтового
file_count, - для каждого файла: 2-байтовой длины имени, саму строку имени, зарезервированные 8 байт под
offsetи 8 байт подcompressed_size.
- байта
-
Цикл по
FileEntry:- для обычного файла — вызов
huffman_compress(...), - для директории — просто записывает нулевые длины (размер = 0).
- после компрессии заполняет зарезервированные в заголовке поля
offsetиcompressed_sizeчерезfseek/fwrite.
- для обычного файла — вызов
-
Возвращает суммарный оригинальный размер (для статистики).
-
-
Основные шаги декомпрессии (
archive_decompress):-
Открытие архива на чтение, чтение
symbol_widthиfile_count. -
Чтение метаданных всех записей (имена, оффсеты, размеры).
-
По селект-списку
sel_files[]или «все, если пусто» — выбор нужных записей. -
Для каждой записи:
- создание вложенных директорий через
mkdirпо путиname, - если
compressed_size > 0— вызовhuffman_decompress(...)с указанием смещения и длины, - иначе — просто создаётся пустой файл или каталог.
- создание вложенных директорий через
-
-
Вспомогательные функции:
static void make_tmp_name(...)— генерирует временное имя для промежуточной записи.void huffman_progress_callback(uint64_t inc)— аккумулирует байты и передаёт их вprogress_update().
-
Задачи: Красивый вывод прогресса (в процентах) в консоль при долгих операциях.
-
Локальные переменные:
static uint64_t total_size = 0; static int last_percentage = -1;
-
Функции:
void progress_start(uint64_t total_bytes)— инициализирует общий объём.void progress_update(uint64_t processed_bytes)— пересчитывает процент и печатает «\rПрогресс: XX%».void progress_end()— доводит индикатор до 100% и переводит строку.