Delphi-Help

Главная

Нетипизированные файлы

Оцените материал
(4 голосов)


Нетипизированные файлы

Третий тип файлов Паскаля, это нетипизированные файлы, этот тип характеризуется тем, что данные имеют не регулярную структуру, например записи различных типов или записи переменной длины, или это просто двоичные данные.

После появления поддержки файлов в VCL, на уровне потоков, а также прямой доступ к файловой системе, через функции АПИ, его ценность стала низкой и почти во всех случаях правильнее использовать потоковые методы из VCL, и только тогда когда требуется небольшой размер программы, стоит использовать их или WinAPI.

Хотя предполагается работа с двоичными данными неопределенного типа, все равно ведется работа с понятиями запись (блок), размер которой задается при открытии файла, по умолчанию размер записи равен 128 байт. Размер файла должен быть кратным размеру блока, иначе при чтении последней записи возникнет ошибка.

Новых понятий немного, это понятие размер блока, режим открытия и вместо процедур Read/Write используются процедуры BlockRead/BlockWrite.

Посмотрим на изменения по отношению к текстовым и типизированным файлам.

Объявление файла делается так:

var 
  FileVar: file;

Сразу бросается в глаза отсутствие типа, вместо file of тип, просто file, то есть файл данных любого типа.

Процедуры открытия файла Reset и ReWrite имеют дополнительный параметр, который указывает размер записи, если этот параметр не указан, то используется значение по умолчанию в 128 байт, кстати, это часто является одной из причин для возникновения ошибок, забываем указать этот размер, а при работе считаем, что работаем с байтом. Что бы работать с файлом, как с байтом, надо или установить размер записи в 1 или использовать типизированный файл следующего типа - file of byte.

При получении размера файла, результат выдается так же в записях, и если опять же нужно получить размер файла в байтах, также надо устанавливать размер записи в единицу или умножить количество записей на размер записи, в этом кстати и состоит различие между файлами Паскаля и файлами в АПИ или VCL, те не оперируют понятиями запись, а только понятиями байт. Другая ошибка у начинающих состоит в том, что длина файла должна быть кратна длине записи, частичные записи не допустимы.

Различие между Reset и Rewrite такое же, как и у нетипизированных файлов, первый открывает файл без уничтожения старого файла, а второй создает новый файл, режим открытия файлов задается отдельно.

Примеры открытия файла с размером записи в 1 байт

Reset(F, 1);  // открытие с сохранением файла, файл должен существовать
Reset(F, 1);  // открытие с созданием нового файла, или с удалением старого

Для управления режимом открытия файлов существует глобальная переменная FileMode. Назначение этой переменной одного из значений влияет на режим последующего открытия файлов, все последующие файлы открываются в соответствии с ее значением, режим ранее открытых файлов не изменяется. Данная переменная не применима для текстовых файлов, которые открываются в соответствии с типом функции, для Reset это режим только чтение, для Rewrite и Append это режим записи.

В модуль SysUtils находится определение констант для потоков, часть констант совпадает с нужными нам. Для полноценного управления режимами доступа надо использовать класс TFileStream.

· fmOpenRead = 0 открытие только в режиме чтения

· fmOpenWrite = 1 открытие только в режиме записи

· fmOpenReadWrite = 2 открытие в режиме чтения/записи

Примечание: Переменная FileMode не является потоко безопасной.

Теперь можно приступить к примерам и поскольку трудно придумать практический пример, то я приведу по три примера использования данного типа и с использованием TFileStream. Это позволит оценить оба метода.

Пример 1 - абстрактные данные (file)

Использование с двоичными данными абстрактного типа. Просто набор байт.

Для демонстрации возьмем простой набор строк, скажем из TStringList. Запись в файл будем производить в следующем формате - длина, строка.

Особого практического применения нет, но данная техника часто используется в потоках. Когда надо передавать данные переменной длины, записывающий поток передает, принимающий поток сначала считывает длину, а затем считывает уже известное количество данных.

Умолчания для примера:

1. SL создан и содержит строки;

2. Переменная FileName инициализирована и содержит имя файла;

3. Обработка ошибок не ведется, кроме необходимых случаев.

var
  SL: TStringList;  
  I: Integer;
  F: file;
  FileName: string;
begin
  try
    AssignFile(F, Filename);  // связали файл с переменной
    FileMode := fmOpenWrite;  // только запись
    Rewrite(F, 1);            // размер записи один байт
    for I := 0 to Sl.Count –1 do  // проход по всем строкам 
    begin
      BlockWrite(F, Length(Sl.Strings[I]), SizeOf(LongInt));
      BlockWrite(F, Sl.Strings[I], Length(Sl.Strings[I]);        
    end;
  finally
    CloseFile(F);
  end;
end.

Пример 2 - абстрактные данные (TFileStream)

var
  SL: TStringList;  
  I: Integer;
  FS: TFileStream;
  FileName: string;
  I: Integer;
 
begin
  FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
  try
    for I := 0 to Sl.Count –1 do  // проход по всем строкам 
    begin
      FS.Write(Length(Sl.Strings[I]), SizeOf(LongInt));
      FS.Write(Sl.Strings[I], Length(Sl.Strings[I]));     
    end;
  finally
    FS.Free;
  end;
end.

Преимущество состоит в использовании расширенных режимов открытия файлов и нет нужды объявлять размер записи, поскольку самих записей не существует, при записи в файл просто указывается длина данных.

Пример 3 - записи фиксированной длины (file)

Использование записей, но разного типа. Обратите внимание, что в записях используется не Integer, а LongInt, это связано с тем, что Integer не является фундаментальным типом и его размер зависит от версии компилятора, в то же время LongInt всегда 4 байта. Также что размер string[3] совпадает с размером LongInt, этим обеспечивается одинаковый размер записи. Вторым параметром, влияющим на размер записи - являет выравнивание элементов записи, на какую либо границу 2, 4, 8 байт, это также предмет для изменений в различных версиях компилятора или его настроек. Использование ключевого слова packed позволяет избежать этой неприятности, в этом случае запись занимает ровно столько место, сколько требуется и не байтом больше. Это обеспечит нам переносимость. Настоятельно рекомендую обратить особое внимания на эти замечания, поскольку это распространенная ошибка при написании программ.

Умолчания для примера:

1. Массив DevArray создан и содержит данные;

2. Переменная FileName инициализирована и содержит имя файла;

3. Обработка ошибок не ведется, кроме необходимых случаев.

type
  TCmd: string[3];     // команда устройству, аббревиатура из 3 символов
 
  TRecType = (rtNone, rtCmd, ctData);
 
  THdr = packed record
    TypeID: TRecType;   // идентификатор записи, 
                        // общая часть во всех остальных типах.
  end;
 
  TCmd = packed record
    Hdr: THdr;          // идентификатор записи
    DevCmd: TCmd;       // команда устройству, аббревиатура из 3 символов
  end;
 
  TData = packed record
    Hdr: THdr;          // идентификатор записи
    DevData: LongInt;   // данные для устройства или из устройства
  end;
 
  TDevEntry = packed record
    Cmd: TCmd;
    Data: LongInt;
  end;
 
var
  Cmd: TCmd;
  Data: Tdata;
  DevArray: array[1..100] of TDevEntry;
  F: file;
  FileName: string;
  I: Integer;
 
begin
  try
    AssignFile(F, Filename);   // связали файл с переменной
    FileMode := fmOpenWrite;   // только запись
    Rewrite(F, SizeOf(TCmd));  // TData имеет тот же размер
    for I := Low(DevArray) to High(DevArray) do  // проход по массиву
    begin
      Cmd.Hdr.TypeID  := rtCmd;
      Cmd.DevCmd      := DevArray[I].Cmd;
      BlockWrite(F, Cmd, SizeOf(TCmd));
      Data.Hdr.TypeID := rtData;
      Data.DevData    := DevArray[I].Data;
      BlockWrite(F, Data, SizeOf(TData));
    end;
  finally
    CloseFile(F);
  end;
end.

Пример 4 - записи фиксированной длины (TFileStream)

Объявления типов прежние.

var
  DevArray: array[1..100] of TDevEntry;
  FS: TFileStream;
  I: Integer;
 
begin
  FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
  try
    for I := Low(DevArray) to High(DevArray) do  // проход по массиву
    begin
      FS.Write(rtCmd, SizeOf(THdr.TypeID));
      FS.Write(DevArray[I].Cmd, SizeOf(TCmd.DevCmd));
      FS.Write(rtData, SizeOf(THdr.TypeID));
      FS.Write(DevArray[I].Data, SizeOf(TData.DevData));
    end;
  finally
    FS.Free;
  end;
end.

Как только код стал сложнне, так сразу стало видно, что использование TFileStream проще и прозрачнее, код более четкий. Отпала необходимости в копировании данных во временные переменные.

Пример 5 - записи переменной длины (file)

Использование записей переменной длины для организации сложных структур. Записи состоят из двух частей, фиксированной с информацией о дальнейших записях и переменной 0 сами записи. Возможно построение сложных иерархических структур, когда одна запись содержит в себе другие вложенные данные, наглядным примером являются объектовые (.obj) и исполнимые файлы (.exe).

Умолчания для примера:

1. Массив DevArray создан и содержит данные;

2. Переменная FileName инициализирована и содержит имя файла;

3. Обработка ошибок не ведется, кроме необходимых случаев.

В качестве основы определим следующие типы:

type
  THdr = packed record
    RecID: TRecType;    // идентификатор записи, необязательная часть,
                        // зависит от задачи, 
                        // но очень полезная в сложных структурах
    RecLg: Integer;     // длина данных, следуют сразу за заголовком
                        // данные могут быть простыми, но также и сложными
                        // то есть включать другие структуры 
                        // со своими заголовками
  end;
 
  TCmd = string[6];
 
  TPacked = packed record
    DevCmd: TCmd;       // команда устройству, аббревиатура из 3 символов
    DevData: string;    // переменная длина
  end;
 
  TDevEntry = packed record
    Cmd: TCmd;
    Data: string;
  end;

В файл будем писать данные в следующим формате

1. Заголовок типа THdr, В качестве RecID будем использовать порядковый номер записи начиная с 1. RecLg будет включать полную длину последующего пакета, размер которого переменный.

2. Данные в формате TPacked, где DevCmd аббревиатура команды из 6 символов (string[6]), фиксированной длины и строковые данные переменной длины. Общая длина пакета отражается в заголовке записи, в поле RecLg.

var
  Hdr: THdr;
  DevArray: array[1..100] of TDevEntry;
  F: file;
  FileName: string;
  I: Integer;
 
begin
  try
    AssignFile(F, Filename);   // связали файл с переменной
    FileMode := fmOpenWrite;   // только запись
    Rewrite(F, 1);             // так как записи переменной длины, 
                               // то размер записи 1 байт
    for I := Low(DevArray) to High(DevArray) do  // проход по массиву
    begin
      Hdr.RecId       := I;
      Hdr.RecLg       := SizeOf(TCmd) + Length(DevArray[I].Data);
      BlockWrite(F, Hdr, SizeOf(THdr)); // записали заголовок
      BlockWrite(F, DevArray[I].Cmd, SizeOf(TCmd));
      BlockWrite(F, DevArray[I].Data[0], Length(DevArray[I].Data);
    end;
  finally
    CloseFile(F);
  end;
end.

В примере происходит следующее:

· Во временную переменную записывается номер записи

· Рассчитывается длина переменного блока

· Заголовок пишется в файл

· Затем в файл пишется фиксированная часть блока DevArray[I].Cmd

· И затем пишется переменная часть блока DevArray[I].Data[0]

Цикл повторяется по всему массиву, по окончанию файл закрывается, теперь реализуем это пример с помощью TFileStream.

Пример 6 - записи переменной длины (TFileStream)

var
  DevArray: array[1..100] of TDevEntry;
  FS: TFileStream;
  I: Integer;
 
begin
  FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
  try
    for I := Low(DevArray) to High(DevArray) do  // проход по массиву
    begin
      FS.Write(I, SizeOf(THdr.RecID));
      FS.Write(SizeOf(TCmd)+Length(DevArray[I].Data),SizeOf(THdr.RecLg));
      FS.Write(DevArray[I].Cmd, SizeOf(TCmd));
      FS.Write(DevArray[I].Data[0], Length(DevArray[I].Data);
    end;
  finally
    FS.Free;
  end;
end.

Опять видим, что код стал проще и прозрачнее. Отпала необходимость во временных переменных.

Прочитано 14417 раз

Авторизация



Счетчики