siryksv
Участник форума
- Регистрация
- 5 Окт 2009
- Сообщения
- 567
- Благодарности
- 1
- Баллы
- 225
В данной теме я покажу, как создать неплохой архиватор с помощью подручных средств. В роли подручных средств я буду использовать Delphi 7, нужна именно 7-я версия. Начиная с Delphi 7, в комплект поставки вместе с исходными кодами входит библиотека ZLib, которая предоставляет достаточно неплохой алгоритм сжатия данных, основанный на методах LZW-компрессии (алгоритмы Зива-Лэмпела). Для использования библиотеки надо подключить модуль zLib в список использованных модулей (uses).
Где это можно применить?
Для понимания сути происходящих процессов, нам понадобятся некоторые предварительные сведения о потоках данных в Delphi. К изложению этих сведений мы и приступаем.
Потоки в Delphi.
В Delphi существует абстрактный класс TStream. Он является очень полезным, так как предоставляет унифицированный доступ к понятию потока чего-либо, каких-то данных. Потоки являются удобным средством для работы с данными самого различного вида. Любой поток может открываться, читать и писать данные, изменять текущее положение указателя, и закрываться. Что значит "изменять положение указателя"? Дело в том, что при работе с TStream, носитель данных представляется в виде такой себе "магнитофонной ленты", и чтобы прочитать определенные данные, надо эту ленту перемотать. Однако вместо перемотки ленты удобнее ввести некоторый "указатель", который будет показывать текущую позицию на носителе, как бы положение "считывающей головки". Такая техника доступа к данным называется последовательной. Другие техники (произвольный доступ, например) нам в данном моем примере не понадобятся.
Итак, класс TStream – это поток абстрактный, и не привязан к конкретным видам данных. В то же время, и ребенку ясно, что, к примеру, для доступа к данным, находящимся в памяти, и находящимся на диске, нужны различные средства. Поэтому в Delphi существуют различные потомки класса TStream, которые уже ведут работу из данными конкретного происхождения. Наиболее часто используемыми являются классы TFileStream, TMemoryStream, TResourceStream, TStringStream, которые предоставляют средства работы из файлами, данными в памяти, ресурсами и строками соответственно.
Класс TFileStream
Класс TFileStream позволяет создать поток для работы с файлами. При этом поток работает с файлом без учета типа хранящихся в нем данных. Полное имя файла задается в параметре FileName при создании потока в конструкторе класса:
Параметр Mode определяет режим работы с файлом. Он составляется из флагов режима открытия:
fmCreate — файл создается;
fmOpenRead — файл открывается для чтения;
fmopenwrite — файл открывается для записи;
fmOpenReadWrite — файл открывается для чтения и записи.
И флагов режима совместного использования:
fmShareExciusive — файл недоступен для открытия другими приложениями;
fmShareDenyWrite — другие приложения могут читать данные из файла;
fmShareDenyRead — другие приложения могут писать данные в файл;
fmShareDenyNone — другие приложения могут производить с файлом любые операции.
Для чтения и записи из потока используются методы Read и Write
Есть интересный метод в классе, унаследованный еще от предка TStream – функция CopyFrom:
Тут Source – исходный поток, откуда копируется, а Count - количество байт, которые нужно скопировать. Если Count поставить равным 0, то будет скопирован весь поток, начиная с нулевой позиции.
Класс TMemoryStream
Класс TMemoryStream обеспечивает сохранение данных в адресном пространстве. При этом методы доступа к этим данным остаются теми же, что и при работе с файловыми потоками. Это позволяет использовать адресное пространство для хранения промежуточных результатов работы приложения, а также при помощи стандартных методов осуществлять обмен данными между памятью и другими физическими носителями.
Чтение/запись данных в память выполняется привычными методами Read и Write.
Также запись данных в память может осуществляться методами:
Дополнительно можно использовать методы записи данных в файл или поток:
Остальные типы потоков нам не понадобятся. Хотя пример использования TResourceStream я уже показывал в теме про построение инсталляторов.
Архивация
Техника работы из библиотекой zLib основана на потоках. Библиотека экспортирует класс TCustomZLibStream - потомок класса TStream. Класс TCustomZLibStream в свою очередь является родителем для TCompressionStream и TDecompressionStream. Работа с этими классами стандартна, и внимания заслуживают только их конструкторы.
Класс TCompressionStream для упаковки данных имеет конструктор
Тут перечисление
– степень сжатия, а Dest – поток назначения, куда будут записываться заархивированные данные.
Класс TDecompressionStream для распаковки имеет конструктор
Тут Source – потока назначения, куда будут записываться распакованные данные.
Классы TCompressionStream и TDecompressionStream работают "на лету" в том смысле, что упаковка/распаковка и запись в принимающий поток осуществляется сразу по мере того как данные поступают на вход класса (для TCompressionStream) или считываются из него (для TDecompressionStream).
Замечание: Для того, чтобы во время работы программы не создавалось впечатление "подвисания" программы, я процедуры архивации и распаковки засунул в отдельные подпроцессы – нити.
Я не использовал встроенные средства в Delphi для работы с нитями, а воспользовался своими любимыми API-функциями. Для создания нити используется CreateThread:
Это определение я скопировал из MSDN, думаю, оно вполне понятно. Для нас важны параметры lpStartAddress, и dwCreationFlags. Параметр lpStartAddress – это указатель на функцию, которая будет выполняться в потоке. Параметр dwCreationFlags – определяет условия создания потока. Если установить сюда значение CREATE_SUSPENDED, то создается поток в состоянии ожидания и не запускается до тех пор, пока не будет вызвана функция ResumeThread. Если это значение нулевое, поток запускается немедленно после создания. В это время, никакие другие значения не поддерживаются. Функция CreateThread вовращает дескриптор потока, который мы можем использовать для различных целей (например, поменять приоритет или "прибить" поток функцией TerminateThread).
Переходим к практике.
Вот собственно, функция архивации:
А вот функция распаковки:
Скрин программы, а также исходники и сама программа находятся во вложениях.
Где это можно применить?
Лично мне это было полезным при создании инсталляторов (для экономии места и уменьшения размера инсталлятора), а также в некоторых сетевых программах, где возникала необходимость передавать файлы. Для уменьшения размера файлов и, соответственно, экономии трафика, я сжимал перед тем, как передавать файл. А на другой стороне распаковывал.
Библиотека ZLib предоставляет только функции для работы с единичными файлами. Этот пример - демонстрационный, и тут реализована работа только с единичными файлами. Для того, чтобы архивировать целые папки, надо программу модифицировать. Это несложно сделать, действовать надо примерно так: рекурсивно обойти вложенные каталоги, сформировать и сохранить в заголовке древовидную структуру, представляющую файлы и каталоги. Размер структуры записать в самое начало архива, и отвести под представление размера фиксированное число байт.
Библиотека ZLib предоставляет только функции для работы с единичными файлами. Этот пример - демонстрационный, и тут реализована работа только с единичными файлами. Для того, чтобы архивировать целые папки, надо программу модифицировать. Это несложно сделать, действовать надо примерно так: рекурсивно обойти вложенные каталоги, сформировать и сохранить в заголовке древовидную структуру, представляющую файлы и каталоги. Размер структуры записать в самое начало архива, и отвести под представление размера фиксированное число байт.
Для понимания сути происходящих процессов, нам понадобятся некоторые предварительные сведения о потоках данных в Delphi. К изложению этих сведений мы и приступаем.
Потоки в Delphi.
В Delphi существует абстрактный класс TStream. Он является очень полезным, так как предоставляет унифицированный доступ к понятию потока чего-либо, каких-то данных. Потоки являются удобным средством для работы с данными самого различного вида. Любой поток может открываться, читать и писать данные, изменять текущее положение указателя, и закрываться. Что значит "изменять положение указателя"? Дело в том, что при работе с TStream, носитель данных представляется в виде такой себе "магнитофонной ленты", и чтобы прочитать определенные данные, надо эту ленту перемотать. Однако вместо перемотки ленты удобнее ввести некоторый "указатель", который будет показывать текущую позицию на носителе, как бы положение "считывающей головки". Такая техника доступа к данным называется последовательной. Другие техники (произвольный доступ, например) нам в данном моем примере не понадобятся.
Итак, класс TStream – это поток абстрактный, и не привязан к конкретным видам данных. В то же время, и ребенку ясно, что, к примеру, для доступа к данным, находящимся в памяти, и находящимся на диске, нужны различные средства. Поэтому в Delphi существуют различные потомки класса TStream, которые уже ведут работу из данными конкретного происхождения. Наиболее часто используемыми являются классы TFileStream, TMemoryStream, TResourceStream, TStringStream, которые предоставляют средства работы из файлами, данными в памяти, ресурсами и строками соответственно.
Класс TFileStream
Класс TFileStream позволяет создать поток для работы с файлами. При этом поток работает с файлом без учета типа хранящихся в нем данных. Полное имя файла задается в параметре FileName при создании потока в конструкторе класса:
Код:
constructor Create(const FileName: string; Mode: Word);
fmCreate — файл создается;
fmOpenRead — файл открывается для чтения;
fmopenwrite — файл открывается для записи;
fmOpenReadWrite — файл открывается для чтения и записи.
И флагов режима совместного использования:
fmShareExciusive — файл недоступен для открытия другими приложениями;
fmShareDenyWrite — другие приложения могут читать данные из файла;
fmShareDenyRead — другие приложения могут писать данные в файл;
fmShareDenyNone — другие приложения могут производить с файлом любые операции.
Для чтения и записи из потока используются методы Read и Write
Есть интересный метод в классе, унаследованный еще от предка TStream – функция CopyFrom:
Код:
function CopyFrom(Source: TStream; Count: Int64): Int64;
Класс TMemoryStream
Класс TMemoryStream обеспечивает сохранение данных в адресном пространстве. При этом методы доступа к этим данным остаются теми же, что и при работе с файловыми потоками. Это позволяет использовать адресное пространство для хранения промежуточных результатов работы приложения, а также при помощи стандартных методов осуществлять обмен данными между памятью и другими физическими носителями.
Чтение/запись данных в память выполняется привычными методами Read и Write.
Также запись данных в память может осуществляться методами:
Код:
procedure LoadFromFile(const FileName: string); // из файла;
procedure LoadFromStream(Stream: TStream) ; // из другого потока.
Код:
procedure SaveToFile(const FileName: string);
procedure SaveToStream(Stream: TStream).
Архивация
Техника работы из библиотекой zLib основана на потоках. Библиотека экспортирует класс TCustomZLibStream - потомок класса TStream. Класс TCustomZLibStream в свою очередь является родителем для TCompressionStream и TDecompressionStream. Работа с этими классами стандартна, и внимания заслуживают только их конструкторы.
Класс TCompressionStream для упаковки данных имеет конструктор
Код:
constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream)
Код:
TCompressionLevel = (clNone, clFastest, clDefault, clMax)
Класс TDecompressionStream для распаковки имеет конструктор
Код:
constructor Create(Source: TStream);
Классы TCompressionStream и TDecompressionStream работают "на лету" в том смысле, что упаковка/распаковка и запись в принимающий поток осуществляется сразу по мере того как данные поступают на вход класса (для TCompressionStream) или считываются из него (для TDecompressionStream).
Замечание: Для того, чтобы во время работы программы не создавалось впечатление "подвисания" программы, я процедуры архивации и распаковки засунул в отдельные подпроцессы – нити.
Я не использовал встроенные средства в Delphi для работы с нитями, а воспользовался своими любимыми API-функциями. Для создания нити используется CreateThread:
Код:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // дескриптор защиты
SIZE_T dwStackSize, // начальный размер стека
LPTHREAD_START_ROUTINE lpStartAddress, // функция потока
LPVOID lpParameter, // указатель переменной, что может передаваться в поток
DWORD dwCreationFlags, // опции создания
LPDWORD lpThreadId // идентификатор потока
);
Переходим к практике.
Вот собственно, функция архивации:
Код:
function TForm1.SaveToZUP(filein,fileout:string;compression:TCompressionLevel):boolean;
var //filein,fileout - входной и выходной файлы;
//compression - степень сжатия
ms:TMemoryStream; //вспомогательный поток, куда будет загружен входной filein
OutFile:TStream; //выходной поток, сюда поток архивации записывает сжатые данные
ZStream: TCustomZLibStream; //поток архивации (взял абстрактный)
filename:string[255]; //имя входного файла, его запишем в заголовок архива
begin
try
ms:=TMemoryStream.Create; //создание потока
ms.LoadFromFile(filein); //грузим входной файл в поток
OutFile:=TFileStream.Create(Fileout,fmCreate); //создадим пустой файл архива
filename:=ExtractFileName(filein); //выделим имя
OutFile.Write(filename,sizeof(filename)); //запишем в заголовок архива имя
try //создание архивирующего потока, запись данных будет идти в OutFile
ZStream:=TCompressionStream.Create(compression,OutFile);
try
ZStream.CopyFrom(ms,0); //копируем весь входной файл в архивирующий поток
//параллельно идет запись в OutFile
finally
ZStream.Free;
end;
finally
ms.free;
OutFile.Free;
result:=true;
end;
except result:=false;
end;
end;
Код:
function TForm1.LoadFromZUP(filein:string):boolean;
var
S,fileout: string[255]; //вспомогательные для хранения имени и полного пути
Count: Integer;
InFile:TStream;OutFile:TMemoryStream; //входной и выходной потоки
ZStream: TCustomZLibStream; //поток деархивации (взял абстрактный)
Buffer: array[0..cBufferSize-1] of Byte; //временной буфер
begin
try
InFile:= TFileStream.Create(filein, fmOpenRead); //открыли на чтение
InFile.Read(S,sizeof(S)); //прочитаем имя файла из заголовка архива
try
OutFile:= TMemoryStream.Create; //создадим выходной поток
try
ZStream:=TDecompressionStream.Create(InFile); //поток распаковки файла, представленного InFile
try
while True do
begin
Count:=ZStream.Read(Buffer,cBufferSize); //читаем распакованные данные в Buffer
if Count <> 0 then OutFile.WriteBuffer(Buffer, Count) //запись в выходной поток
else Break;
end;
finally ZStream.Free;
end;
finally
end;
finally
InFile.Free;
try
fileout:=ExtractFilePath(filein)+S; //полный путь к разархивированному файлу
OutFile.SaveToFile(fileout); //сохраним наш выходной поток в файл
finally
OutFile.Free;
end;
end;
except result:=false;
end;
result:=true;
end;