сохранение записей, содержащих член типа string, в файл (Delphi, Windows)
Вопрос
У меня есть запись, которая выглядит примерно так:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
Простой.Причина, по которой я решил установить переменные в виде строки (а не массива символов), заключается в том, что я понятия не имею, какой длины будут эти строки.Они могут иметь длину 1 символ, 200 или 2000.Конечно, когда я пытаюсь сохранить запись в файл типа (файл...), компилятор жалуется, что мне нужно указать размер строки.Есть ли способ преодолеть это?или способ сохранить эти записи в нетипизированный файл и при этом сохранить своего рода возможность поиска?
Пожалуйста, не указывайте мне на возможные решения, если вы знаете решение, пришлите код.Спасибо
Решение
Вы не можете сделать это с типизированным файлом.Попробуйте что-то вроде этого с TFileStream:
type
TStreamEx = class helper for TStream
public
procedure writeString(const data: string);
function readString: string;
procedure writeInt(data: integer);
function readInt: integer;
end;
function TStreamEx.readString: string;
var
len: integer;
iString: UTF8String;
begin
self.readBuffer(len, 4);
if len > 0 then
begin
setLength(iString, len);
self.ReadBuffer(iString[1], len);
result := string(iString);
end;
end;
procedure TStreamEx.writeString(const data: string);
var
len: cardinal;
oString: UTF8String;
begin
oString := UTF8String(data);
len := length(oString);
self.WriteBuffer(len, 4);
if len > 0 then
self.WriteBuffer(oString[1], len);
end;
function TStreamEx.readInt: integer;
begin
self.readBuffer(result, 4);
end;
procedure TStreamEx.writeInt(data: integer);
begin
self.WriteBuffer(data, 4);
end;
type
TNote = record
Title : string;
Note : string;
Index : integer;
procedure Save(stream: TStream);
end;
procedure TNote.Save(stream: TStream);
var
temp: TMemoryStream;
begin
temp := TMemoryStream.Create;
try
temp.writeString(Title);
temp.writeString(Note);
temp.writeInt(Index);
temp.seek(0, soFromBeginning);
stream.writeInt(temp.size);
stream.copyFrom(temp, temp.size);
finally
temp.Free;
end;
end;
Я оставлю процедуру загрузки вам.Та же основная идея, но временный поток не нужен.Благодаря размеру записи перед каждой записью вы можете прочитать ее и узнать, как далеко нужно пропустить, если вы ищете определенную запись # вместо того, чтобы читать ее целиком.
РЕДАКТИРОВАТЬ:Это было написано специально для версий Delphi, использующих строки Unicode.В более старых версиях это можно было немного упростить.
Другие советы
Почему бы не записать это в формате XML?Смотрите мою сессию»Практический XML с Delphi"о том, как с этим начать.
Другая возможность — превратить ваши записи в классы, убывающие от TComponent, и хранить/извлекать ваши данные в файлах DFM.
Эта запись Stackoverflow показывает, как это сделать.
--джероен
ПС:Извините, мой ответ XML был немного запутанным;Вообще-то я еду на две конференции (БАСТА! и ДельфиЖиви!Германия).
По сути, то, что вам нужно сделать, очень просто:создайте образец XML-файла, затем запустите Мастер привязки XML-данных Delphi (доступно в Delphi начиная с версии 6).
Этот мастер сгенерирует для вас модуль, который имеет интерфейсы и классы, сопоставляющие XML с объектами Delphi, а также несколько вспомогательных функций для их чтения из файла, создания нового объекта и т. д.Моя сессия (см. первую ссылку выше) фактически содержит большую часть деталей этого процесса.
По приведенной выше ссылке представлено видео, демонстрирующее использование мастера привязки XML-данных Delphi.
Вы можете работать с двумя разными файлами: один просто хранит строки каким-либо удобным способом, а другой хранит записи со ссылкой на строки.Таким образом, у вас по-прежнему будет файл записей для быстрого доступа, даже если вы не знаете размер фактического содержимого.
(К сожалению, кода нет.)
TNote = record
Title : string;
Note : string;
Index : integer;
end;
можно перевести как
TNote = record
Title : string[255];
Note : string[255];
Index : integer;
end;
и используйте Stream.writebuffer(ANodeVariable, sizeof(TNode), но вы сказали, что в этом случае строки превышают 255 символов, ЕСЛИ строка превышает 65535 символов, тогда измените WORD на INTEGER
type
TNodeHeader=Record
TitleLen,
NoteLen: Word;
end;
(* this is for writing a TNode *)
procedure saveNodetoStream(theNode: TNode; AStream: TStream);
var
header: TNodeHeader;
pStr: PChar;
begin
...
(* writing to AStream which should be initialized before this *)
Header.TitleLen := Length(theNode.Title);
header.NodeLen := Length(theNode.Note);
AStream.WriteBuffer(Header, sizeof(TNodeHeader);
(* save strings *)
PStr := PChar(theNode.Title);
AStream.writeBuffer(PStr^, Header.TitleLen);
PStr := PChar(theNode.Note);
AStream.writebuffer(PStr^, Header.NoteLen);
(* save index *)
AStream.writebuffer(theNode.Index, sizeof(Integer));
end;
(* this is for reading a TNode *)
function readNode(AStream: TStream): TNode;
var
header: THeader
PStr: PChar;
begin
AStream.ReadBuffer(Header, sizeof(TNodeHeader);
SetLength(Result.Title, Header.TitleLen);
PStr := PChar(Result.Title);
AStream.ReadBuffer(PStr^, Header.TitleLen);
SetLength(Result.Note, Header.NoteLen);
PStr := PChar(Result.Note);
AStream.ReadBuffer(PStr^, Header.NoteLen);
AStream.ReadBuffer(REsult.Index, sizeof(Integer)(* 4 bytes *);
end;
Вы можете использовать функции доступен в этом модуле с открытым исходным кодом.
Он позволяет сериализовать любое содержимое записи в двоичный формат, включая даже динамические массивы внутри:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
var
aSave: TRawByteString;
aNote, aNew: TNote;
begin
// create some content
aNote.Title := 'Title';
aNote.Note := 'Note';
aNote.Index := 10;
// serialize the content
aSave := RecordSave(aNote,TypeInfo(TNote));
// unserialize the content
RecordLoad(aNew,pointer(aSave),TypeInfo(TNote));
// check the content
assert(aNew.Title = 'Title');
assert(aNew.Note = 'Note');
assert(aNew.Index = 10);
end;