У меня довольно часто возникает ситуация, когда в проекте необходимо организовать сохранение каких либо данных (объектов, типов, записей) в файл. Существует несколько методов решения этого вопроса. Я лично рассматривал возможность сохранения в XML, сериализацию, сохранение блока памяти и последовательное сохранение. Я называю их так.
Существенное отличие сохранения в XML-подобные форматы заключается в том, что сохраненные файлы являются открытыми и могут меняться как пользователями, так и сторонним программным обеспечением, конечно при наличии соответствующей документации. В каких то случаях это плюс, а в каких то минус. Зависит от конкретной задачи.
Мной будет рассмотрен метод сохранения в нетипизированный файл. Плюсы данного метода относительная простота реализации и возможность сохранения любой информации (вплоть до аудио и видео файлов). Без минусов тоже не обошлось — строгое соблюдение прямой записи и считывания, необходимость переписывать процедуры сохранения/чтения при изменении объекта (например добавление новых полей), а также проблемы с обратной совместимостью при изменении версии сохраняемых файлов. Хотя большая часть проблем может быть решена в логике самого приложения.
Данный метод рассматривается именно как достаточно простой как для понимания, так и для реализации.
Ваш объект, тип, или запись должна быть разбита на простые составляющие и впоследствии записаны в файл. Как это делается. Например строковую переменную можно записать в файл следующим образом:
procedure WriteToFile(var F: file; const str: string);
var
Len: LongInt;
begin
Len := Length(str);
BlockWrite(F, Len, SizeOf(Len));
if Len > 0 then
begin
BlockWrite(F, Str[1], Length(Str) * SizeOf(Str[1]));
end;
end;
Как видите для сохранения типа String необходимо сначала записать в файл длину строки, а потом последовательно саму строку. Обратите внимание, что переменная F типа file должна быть открыта, с установленным указателем. Как это сделать я покажу дальше. Кстати существуют и другие способы записи строк как в память так и в файл. Навскидку могу вспомнить строки фиксированной длины (в Delphi это ShortString по моему) этот тип строк имеет фиксированную длину, а следовательно не требует записи своей длины, т.к. занимает в памяти всегда один и тот же объем. Так же в Windows широко применяются Null-terminated string, строки которые заканчиваются нулевым кодом и то же не требуют указания своей длины.
Рассказываю все это потому, что вы должны четко понимать как в памяти хранятся так называемые простые типы (строки, целые числа и т.п.). В конечном итоге все составные и сложные объекты состоят из этих «кирпичиков».
Приведу еще пример сохранения числа типа Integer, так как тут есть небольшой нюанс.
procedure WriteToFile(var F: file; const Int: Integer);
var
LInt: LongInt;
begin
LInt := Int;
BlockWrite(F, LInt, SizeOf(LInt));
end;
Как видите при сохранении целого числа мы приводим его сначала к типу LongInt. Это делается по причине того, что тип Integer может изменяться в зависимости от версии Delphi, а также насколько я помню от конфигурации операционной системы. Точнее не сам тип, а способ его представления в памяти компьютера. В общем LongInt это фиксированный целый тип со знаком, который не будет изменяться в будущих выпусках Delphi.
Теперь если вы захотите сохранить в файл динамический массив целых чисел, можно воспользоваться следующей процедурой:
procedure WriteToFile(var F: file; const IntegerArray: TIntArray);
var
i: Integer;
Len: LongInt;
begin
Len := Length(IntegerArray);
BlockWrite(F, Len, SizeOf(Len));
if Len > 0 then
begin
for i := Low(IntegerArray) to High(IntegerArray) do
begin
WriteToFile(F, IntegerArray[i]);
end;
end;
end;
Ниже показано как можно сохранить в ваш файл изображение в формате JPEG. Важно понимать, что даже если в файл будет сохранено одно только изображение, редактором оно уже не откроется, нужно будет извлекать его из файла таким же образом. Именно поэтому данный способ сохранения подходит только для составных типов. Например когда вы хотите сохранить картинку вместе с какой то информацией:
procedure WriteToFile(var F: file; const Image: TJPEGImage);
var
Len: LongInt;
ImageStream: TBytesStream;
begin
ImageStream := TBytesStream.Create;
Image.SaveToStream(ImageStream);
Len := Length(ImageStream.Bytes);
BlockWrite(F, Len, SizeOf(Len));
BlockWrite(F, ImageStream.Bytes[0], Len);
ImageStream.Free;
end;
И в конце, как обещал общая процедура сохранения в файл например массива целых чисел и изображения в формате Jpeg:
procedure Save(const FileName: string; const IntegerArray: TIntArray; const Image: TJPEGImage);
var
F: file;
begin
Result := False;
AssignFile(F, FileName);
Rewrite(F, 1);
try
WriteToFile(F, IntegerArray );
WriteToFile(F, Image );
finally
CloseFile(F);
end;
end;
В процедуру передается имя файла, динамический массив целых чисел и изображение в формате Jpeg. Как видите все просто. Главное помните, что чтение всех данных должно происходить строго в том же порядке, что и запись. Для удобства метод WriteToFile я делаю перегруженным, и в зависимости от типа переданного аргумента, компилятор сам выбирает метод сохранения в файл. Пример достаточно примитивен, но отражает суть процесса в целом. Например можно было создать запись состоящую из массива целых чисел и картинки, реализовать WriteToFile для нее и использовать в дальнейшем. Примерно вот так:
// создаем запись вида массив плюс изображение
TIntegerImage = Record
IntegerArray: TIntArray;
Image: TJPEGImage
end;
// пишем метод для записи TIntegerImage в файл, используя ранее описанные методы WriteToFile
// для массива целых чисел и изображения Jpeg
procedure WriteToFile(var F: file; const IntegerImage: TIntegerImage);
begin
WriteToFile(F, IntegerImage.IntegerArray );
WriteToFile(F, IntegerImage.Image );
end;
// общая процедура сохранения в файл типа TIntegerImage
procedure Save(const FileName: string; const IntegerImage: TIntegerImage);
var
F: file;
begin
Result := False;
AssignFile(F, FileName);
Rewrite(F, 1);
try
WriteToFile(F, IntegerImage );
finally
CloseFile(F);
end;
end;
Возможно эта информация будет кому нибудь полезна.