任何时候您需要将可变长度数据存储为二进制格式,您应该将数据的长度存储在实际数据的前面。
由于您以后还需要添加新字段,因此您应该存储每条记录的字段数(或至少在末尾有一个记录结束标记),以便在文件中移动时保持正确的位置读取和查找操作。
至于实际的记录数据,我会建议每个字段的类型-长度-数据格式,这样您就可以在不知道它们的数据类型的情况下提前添加新字段,并允许代码识别和读取/无论内容如何,都可以根据需要跳过各个字段(即,如果旧应用尝试读取具有较新字段的文件,它可以跳过它无法识别的内容)。
最后,您将以这样的方式结束,然后您可以根据需要对其进行扩展、优化等:
const
cTypeUnknown = $00;
cTypeString = $01;
cTypeInteger = $02;
cTypeByte = $03;
cTypeArray = $80;
cTypeStringArray = cTypeStringArray or cTypeArray;
cTypeIntegerArray = cTypeIntegerArray or cTypeArray;
cTypeByteArray = cTypeByteArray or cTypeArray;
type
Streamable = class
public
procedure Read(Stream: TStream); virtual; abstract;
procedure Write(Stream: TStream); virtual; abstract;
end;
Field = class(Streamable)
public
function GetType: Byte; virtual; abstract;
end;
FieldClass = class of Field;
StringField = class(Field)
public
Data: String;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
StringArrayField = class(Field)
public
Data: array of String;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
IntegerField = class(Field)
public
Data: Integer;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
IntegerArrayField = class(Field)
public
Data: array of Integer;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
ByteField = class(Field)
public
Data: Byte;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
ByteArrayField = class(Field)
public
Data: array of Byte;
function GetType: Byte; override;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
AnyField = class(ByteArrayField)
public
Type: Byte;
function GetType: Byte; override;
end;
Record = class(Streamable)
public
Fields: array of Field;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
RecordArray = class(Streamable)
public
Records: array of Record;
procedure Read(Stream: TStream); override;
procedure Write(Stream: TStream); override;
end;
procedure WriteByte(Stream: TStream; Value: Byte);
begin
Stream.WriteBuffer(@Value, SizeOf(Byte));
end;
function ReadByte(Stream: TStream): Byte;
begin
Stream.ReadBuffer(@Result, SizeOf(Byte));
end;
procedure WriteInteger(Stream: TStream; Value: Integer);
begin
Stream.WriteBuffer(@Value, SizeOf(Integer));
end;
function ReadInteger(Stream: TStream): Integer;
begin
Stream.ReadBuffer(@Result, SizeOf(Integer));
end;
procedure WriteString(Stream: TStream; Value: String);
var
S: UTF8String;
begin
S := UTF8Encode(Value);
WriteInteger(Stream, Length(S));
if Length(S) > 0 then
Stream.WriteBuffer(S[1], Length(S));
end;
function ReadString(Stream: TStream): String;
var
S: UTF8String;
begin
SetLength(S, ReadInteger(Stream));
if Length(S) > 0 then
Stream.ReadBuffer(S[1], Length(S));
Result := UTF8Decode(S);
end;
function StringField.GetType: Byte;
begin
Result := cTypeString;
end;
procedure StringField.Read(Stream: TStream);
begin
Data := ReadString(Stream);
end;
procedure StringField.Write(Stream: TStream);
begin
WriteString(Data);
end;
function StringArrayField.GetType: Byte;
begin
Result := cTypeStringArray;
end;
procedure StringArrayField.Read(Stream: TStream);
var
I: Integer;
begin
SetLength(Data, ReadInteger(Stream));
for I := 0 to High(Data) do
Data[I] := ReadString(Stream);
end;
procedure StringArrayField.Write(Stream: TStream);
var
I: Integer;
begin
WriteInteger(Stream, Length(Data));
for I := 0 to High(Data) do
WriteString(Stream, Data[I]);
end;
procedure IntegerField.GetType: Byte;
begin
Result := cTypeInteger;
end;
procedure IntegerField.Read(Stream: TStream);
begin
Assert(ReadInteger(Stream) == SizeOf(Integer));
Data := ReadInteger(Stream);
end;
procedure IntegerField.Write(Stream: TStream);
begin
WriteInteger(Stream, SizeOf(Integer));
WriteInteger(Stream, Data);
end;
function IntegerArrayField.GetType;
begin
Result := cTypeIntegerArray;
end;
procedure IntegerArrayField.Read(Stream: TStream);
var
Num: Integer;
begin
I := ReadInteger(Stream);
Assert((I mod SizeOf(Integer)) == 0);
SetLength(Data, I);
if Length(Data) > 0 then
Stream.ReadBuffer(Data[0], I * SizeOf(Integer));
end;
procedure IntegerArrayField.Write(Stream: TStream);
begin
WriteInteger(Stream, Length(Data));
if Length(Data) > 0 then
Stream.WriteBuffer(Data[0], Length(Data) * SizeOf(Integer));
end;
procedure ByteField.GetType: Byte;
begin
Result := cTypeByte;
end;
procedure ByteField.Read(Stream: TStream);
begin
Assert(ReadInteger(Stream) == SizeOf(Byte));
Data := ReadByte(Stream);
end;
procedure ByteField.Write(Stream: TStream);
begin
WriteInteger(Stream, SizeOf(Byte));
WriteByte(Stream, Byte);
end;
function ByteArrayField.GetType: Byte;
begin
Result := cTypeByteArray;
end;
procedure ByteArrayField.Read(Stream: TStream);
begin
SetLength(Data, ReadInteger(Stream));
if Length(Data) > 0 then
Stream.ReadBuffer(Data[0], Length(Data));
end;
procedure ByteArrayField.Write(Stream: TStream); override;
begin
WriteInteger(Stream, Length(Data));
if Length(Data) > 0 then
Stream.WriteBuffer(Data[0], Length(Data));
end;
function AnyField.GetType: Byte;
begin
Result := Type;
end;
procedure Record.Read(Stream: TStream);
const
PlainTypes = array[1..3] of FieldClass = (StringField, IntegerField, ByteField);
ArrayTypes = array[1..3] of FieldClass = (StringArrayField, IntegerArrayField, ByteArrayField);
var
I: Integer;
RecType, PlainType: Byte;
begin
SetLength(Fields, ReadInteger(Stream));
for I := 0 to High(Fields) do
begin
RecType := ReadByte(Stream);
PlainType := RecType and (not cTypeArray);
if (PlainType >= cTypeString) and (PlainType <= cTypeByte) then
begin
if (RecType and cTypeArray) <> cTypeArray then
Fields[I] := PlainTypes[PlainType].Create
else
Fields[I] := ArrayTypes[PlainType].Create;
end else
Fields[I] := AnyField.Create;
Fields[I].Read(Stream);
end;
end;
procedure Record.Write(Stream: TStream)
var
I: Integer;
begin
WriteInteger(Stream, Length(Fields));
for I := 0 to High(Fields) do
begin
WriteByte(Stream, Fields[I].GetType);
Fields[I].Write(Stream);
end;
end;
procedure RecordArray.Read(Stream: TStream);
var
I: Integer;
begin
SetLength(Records, ReadInteger(Stream));
for I := High(Records) do
begin
Records[I] := Record.Create;
Records[I].Read(Stream);
end;
end;
procedure RecordArray.Write(Stream: TStream);
begin
WriteInteger(Stream, Length(Records));
for I := High(Records) do
Records[I].Write(Stream);
end;