Для восстановления LZ77 целесообразно создать специальный класс хеш-таблице, поскольку в ней должен выполняться ряд специализированных операций. Код этого класса хеш-таблицы приведен в листинге 11.25.
Листинг 11.25. Хеш-таблица LZ77
type
TtdLZSigEnumProc =procedure (aExtraData : pointer;
const aSignature : TtdLZSignature;
aOffset : longint);
PtdLZHashNode = ^TtdLZHashNode;
TtdLZHashNode = packed record hnNext : PtdLZHashNode;
hnSig : TtdLZSignature;
hnOffset : longint;
end;
type
TtdLZHashTable = class private
FHashTable : TList;
FName : TtdNameString;
protected
procedure htError(aErrorCode : integer;
const aMethodName : TtdNameString);
procedure htFreeChain( aParentNode : PtdLZHashNode );
public
constructor Create;
destructor Destroy; override;
procedure Empty;
function EnumMatches(const aSignature : TtdLZSignature;
aCutOffset : longint; aAction : TtdLZSigEnumProc;
aExtraData : pointer): boolean;
procedure Insert(const aSignature : TtdLZSignature; aOffset : longint);
property Name : TtdNameString read FName write FName;
end;
constructor TtdLZHashTable.Create;
var
Inx : integer;
begin
inherited Create;
if (LZHashNodeManager = nil) then begin
LZHashNodeManager := TtdNodeManager.Create(sizeof(TtdLZHashNode));
LZHashNodeManager.Name := 1LZ77 node manager1;
end;
{создать хеш-таблицу, преобразовать все элементы в связные списки с фиктивным заглавным узлом}
FHashTable := TList.Create;
FHashTable.Count := LZHashTableSize;
for Inx := 0 to pred(LZHashTableSize) do FHashTable.List^[Inx] := LZHashNodeManager.AllocNodeClear;
end;
destructor TtdLZHashTable.Destroy;
var
Inx : integer;
begin
{полностью уничтожить хеш-таблицу, включая фиктивные заглавные узлы}
if (FHashTable <> nil) then begin
Empty;
for Inx := 0 to pred(FHashTable.Count) do
LZHashNodeManager.FreeNode(FHashTable.List^[Inx]);
FHashTable.Free;
end;
inherited Destroy;
end;
procedure TtdLZHashTable.Empty;
var
Inx : integer;
begin
for Inx := 0 to pred(FHashTable.Count) do htFreeChain(PtdLZHashNode(FHashTable.List^[Inx]));
end;
function TtdLZHashTable.EnumMatches( const aSignature : TtdLZSignature;
aCutOffset : longint;
aAction : TtdLZSigEnumProc;
aExtraData : pointer): boolean;
var
Inx : integer;
Temp : PtdLZHashNode;
Dad : PtdLZHashNode;
begin
{предположим, что ни один элемент не найден}
Result := false;
{вычислить индекс хеш-таблицы для этой сигнатуры}
Inx := (aSignature.AsLong shr 8) mod LZHashTableSize;
{выполнить обход цепочки, расположенной в позиции с этим индексом}
Dad := PtdLZHashNode (FHashTable.List^[Inx]);
Temp := Dad^.hnNext;
while (Temp <> nil) do
begin
{если смещение этого узла меньше значения смещения, по которому выполняется усечение, остальная часть цепочки удаляется, и выполняется выход из подпрограммы}
if (Temp^.hn0ffset < aCutOffset) then begin
htFreeChain(Dad);
Exit;
end;
{если сигнатура узла совпадает с данной сигнатурой, выполняется вызов подпрограммы, выполняющей действие}
if (Temp^.hnSig.AsLong = aSignature.AsLong) then begin
Result true;
aAction(aExtraData, aSignature, Temp^.hnOffset);
end;
(перешли к следующему узлу) Dad := Temp;
Temp := Dad^.hnNext;
end;
end;
procedure TtdLZHashTable.htFreeChain(aParentNode : PtdLZHashNode);
var
Walker, Temp : PtdLZHashNo4e;
begin
Walker := aParentNode^.hnNext;
aParentNode^.hnNext := nil;
while (Walker <> nil) do
begin
Temp := Walker;
Walker := Walker^.hnNext;
LZHashNodeManager.FreeNode(Temp);
end;
end;
procedure TtdLZHashTable.Insert(const aSignature : TtdLZSignature;
aOffset : longint);
var
Inx : integer;
NewNode : PtdLZHashNode;
HeadNode : PtdLZHashNode;
begin
{вычислить индекс хеш-таблицы для этой сигнатуры}
Inx := (aSignature.AsLong shr 8) mod LZHashTableSize;
{распределить новый узел и вставить его в начало цепочки, расположенной в позиции хеш-таблицы, определяемой этим индексом; тем самым обеспечивается упорядочение узлов в цепочке в порядке убывания значений смещения}
NewNode := LZHashNodeManager.AllocNode;
NewNode^.hnSig := aSignature;
NewNode^.hnQffset :=a0ffset;
HeadNode := PtdLZHashNode(FHashTable.List^[Inx]);
NewNode^.hnNext := HeadNode^.hnNext;
HeadNode^.hnNext := NewNode;
end;
В целях повышения эффективности в хеш-таблице используется диспетчер узлов, поскольку придется распределить и освободить несколько тысяч узлов. Это выполняется внутри конструктора Create. Через непродолжительное время метод EnumMatches вызывается снова. Он просматривает все элементы в хеш-таблице на предмет совпадения с конкретной сигнатурой и для каждого найденного такого элемента вызывает процедуру aAction. Так реализуется основная логика установления соответствия алгоритма LZ77.
Класс скользящего окна выполняет также ряд важных функций, кроме сохранения ранее встречавшихся байтов. Во-первых, во время кодирования скользящее окно считывает данные из входного потока большими боками, чтобы об этом не нужно было беспокоиться во время выполнения подпрограммы сжатия. Во-вторых, оно возвращает текущую сигнатуру и ее смещение во входном потоке. Третий метод выполняет сопоставление: он принимает смещение во входном потоке, преобразует его в смещение в буфере скользящего окна, а затем сравнивает хранящиеся там символы с символами в текущей позиции. Метод будет возвращать количество совпадающих символов и значение расстояния, что позволяет создать пару расстояние/длина. Заключительный фрагмент кода реализации этого класса скользящего окна приведен в листинге 11.26 (код остальных методов можно найти в листинге 11.23).