Фундаментальные алгоритмы и структуры данных в Delphi
Шрифт:
end;
{задать значения остальных стандартных полей}
with FHeaderRec^ do
begin
hrSignature := cRSSignature;
hrVersion := $00010000; {Major=1; Minor=0}
hrRecordLen := aRecordLen;
hrCapacity := 0;
hrCount := 0;
hr1stDelRec := cEndOfDeletedChain;
end;
{обновить служебный заголовок}
rsSeekStream(FZeroPosition);
rsWriteStream(FHeaderRec^, FHeaderRec^.hrHeaderLen);
{задать значение поля длины записи}
FRecordLen := aRecordLen;
end;
procedure TtdRecordStream.rsReadHeaderRec;
var
StreamSize : longint;
TempHeaderRec : TtdRSHeaderRec;
begin
{если
StreamSize := FStream.Size - FZeroPosition;
if (StreamSize < sizeof(TtdRSHeaderRec)) then
rsError(tdeRSNoHeaderRec, 'rsReadHeaderRec', 0);
{считать служебный заголовок}
rsSeekStream(FZeroPosition);
rsReadStream(TempHeaderRec, sizeof(TtdRSHeaderRec));
{первая санитарная проверка: сигнатура и счетчик/емкость}
with TempHeaderRec do
begin
if (hrSignatureocRSSignature) or (hrCount > hrCapacity) then
rsError(tdeRSBadHeaderRec, 'rsReadHeaderRec', 0);
end;
{выделить память под реальный служебный заголовок, скопировать уже считанные данные}
FHeaderRec := AllocMem(TempHeaderRec.hrHeaderLen);
Move(TempHeaderRec, FHeaderRec^, TempHeaderRec.hrHeaderLen);
{вторая санитарная проверка: проверка данных записи}
with FHeaderRec^ do
begin
FRecordLen4 := hrRecordLen + 4;
{for rsCalcRecordOffset}
if (StreamSize <> rsCalcRecordOffset(hrCapacity)) then
rsError(tdeRSBadHeaderRec, 'rsReadHeaderRec', 0);
{установить значения полей класса}
FCount :=hrCount;
FCapacity := hrCapacity;
FRecordLen := hrRecordLen;
end;
end;
function TtdRecordStream.rsCalcRecordOffset(aIndex : longint): longint;
begin
Result := FZeroPosition + FHeaderRec^.hrHeaderLen + (aIndex * FRecordLen4);
end;
Приведенный метод создания служебного заголовка вызывается только в случае, когда поток пуст. Принцип его работы очень прост. Сначала служебный заголовок создается в памяти, а затем записывается в поток. Если длина записи больше, чем нормальный размер служебного заголовка, его размер увеличивает до размера записи. В служебном заголовке содержится семь полей: поле сигнатуры, которое может использоваться для контроля при считывании записи;
номер версии служебного заголовка (это позволит в будущем добавлять в заголовок новые поля и сохранять совместимость версий);
длина служебного заголовка;
длина записи;
емкость потока (т.е. количество записей как активных, так и удаленных, которые в данный момент находятся в потоке);
количество активных записей;
и, наконец, порядковый номер первой удаленной записи (здесь значение этого поля устанавливается равным cEndOfDetectedChain или -2).
Метод считывания
Метод rsCalcRecordOffset просто вычисляет смещение записи, порядковый номер которой передан ему во входном параметре. При этом учитывается начальное положение потока и размер служебного заголовка.
Листинг 2.22. Добавление новой записи в постоянный массив
function TtdRecordStream.Add(var aRecord): longint;
begin
{если цепочка удаленных записей пуста, в поток добавляется новая запись}
if (FHeaderRec^.hr1stDelRec = cEndOfDeletedChain) then begin
Result :=FCapacity;
inc(FCapacity);
inc(FHeaderRec^.hrCapacity);
end
{в противном случае используется первая удаленная запись, обновляется значение поля удаленной записи в служебном заголовке для указания на следующую удаленную запись}
else begin
Result := FHeaderRec^.hr1stDelRec;
rsSeekStream(rsCalcRecordOffset(FHeaderRec^.hr1stDelRec))/ rsReadStream(FHeaderRec^.hr1stDelRec, sizeof(longint));
end;
{определить смещение записи и сохранить новую запись}
rsSeekStream(rsCalcRecordOffset(Result));
PLongint(FRecord)^ := cActiveRecord;
Move(aRecord, FRecord^[sizeof(longint)], FRecordLen);
rsWritestream(FRecord^, FRecordLen4);
{количество записей увеличилось на единицу}
inc(FCount);
inc(FHeaderRec^.hrCount);
{обновить служебный заголовок}
rsSeekStream(FZeroPosition);
rsWriteStream(FHeaderRec^, sizeof(TtdRSHeaderRec));
end;
Если цепочка удаленных записей не пуста, определить первую удаленную запись (именно поверх нее будет сохраняться новая запись). Мы считываем флаг удаления для этой записи и обновляем поле первой удаленной записи служебного заголовка. Затем мы определяем положение начала удаленной записи, устанавливаем значение флага удаления равным cActiveRecord (-1) и сохраняем запись, переданную методу во входном параметре.
При считывании и сохранении записей необходимо учитывать, удалена ли требуемая запись. Записи идентифицируются по их порядковым номерам.
Листинг 2.23. Чтение и обновление записи в постоянном массиве
procedure TtdRecordStream.Read(aIndex : longint; var aRecord; var alsDeleted : boolean);
begin
{проверить, действителен ли порядковый номер записи}
if (aIndex < 0) or (aIndex >= Capacity) then
rsError(tdeRSOutOfBounds, 'Read', aIndex);