201 lines
5.2 KiB
ObjectPascal
201 lines
5.2 KiB
ObjectPascal
uses BlockFileOfT;
|
||
uses System.Runtime.InteropServices;//нужно чтоб не писать System.Runtime.InteropServices.StructLayout и т.п.
|
||
uses FileArray;
|
||
|
||
type
|
||
DataType1 = record
|
||
b: byte;
|
||
|
||
constructor(b:byte) :=
|
||
self.b := b;
|
||
|
||
end;
|
||
DataType2 = record
|
||
i: integer;
|
||
|
||
constructor(i:integer) :=
|
||
self.i := i;
|
||
|
||
end;
|
||
DataType3 = record
|
||
ch: char;
|
||
|
||
constructor(ch:char) :=
|
||
self.ch := ch;
|
||
|
||
end;
|
||
DataType4 = record
|
||
r: real;
|
||
|
||
constructor(r:real) :=
|
||
self.r := r;
|
||
|
||
end;
|
||
|
||
DataType = (
|
||
ByteData = 1,
|
||
IntData = 2,
|
||
CharData = 3,
|
||
RealData = 4
|
||
);
|
||
|
||
[StructLayout(LayoutKind.&Explicit)] // Позволяет явно указывать позицию каждого поля
|
||
DataUnit = record
|
||
[FieldOffset(0)] DataT: byte;
|
||
[FieldOffset(8)] DataT1: DataType1; // У этих 4 полей одинаковая позиция
|
||
[FieldOffset(8)] DataT2: DataType2; // Значит, у них будет общая память
|
||
[FieldOffset(8)] DataT3: DataType3; // Но это так же значит что если записать данные 1 типа -
|
||
[FieldOffset(8)] DataT4: DataType4; // данные другого типа считать не выйдет (выведет мусор)
|
||
|
||
constructor(data: DataType1);
|
||
begin
|
||
DataT := 1;
|
||
DataT1 := data;
|
||
end;
|
||
|
||
constructor(data: DataType2);
|
||
begin
|
||
DataT := 2;
|
||
DataT2 := data;
|
||
end;
|
||
|
||
constructor(data: DataType3);
|
||
begin
|
||
DataT := 3;
|
||
DataT3 := data;
|
||
end;
|
||
|
||
constructor(data: DataType4);
|
||
begin
|
||
DataT := 4;
|
||
DataT4 := data;
|
||
end;
|
||
|
||
function ToString:string; override;
|
||
begin
|
||
var sb := new StringBuilder;
|
||
|
||
sb.AppendFormat(
|
||
'DataUnit({0,8}, ',
|
||
System.Enum.GetName(typeof(DataType), DataT)
|
||
);
|
||
|
||
case DataType(DataT) of
|
||
ByteData: sb.AppendFormat('{0})',DataT1.b);
|
||
IntData : sb.AppendFormat('{0})',DataT2.i);
|
||
CharData: sb.AppendFormat('{0})',DataT3.ch);
|
||
RealData: sb.AppendFormat('{0})',DataT4.r);
|
||
else raise new System.FormatException($'Не правильный тип данных: {DataT}');
|
||
end;
|
||
|
||
Result := sb.ToString;
|
||
end;
|
||
|
||
end;
|
||
|
||
Database = class(System.IDisposable)
|
||
private const HeaderSize = 1 + 4*2; // 1 байт на версию + 4 символа (по 2 байта) на DatabaseType
|
||
|
||
public version: byte;
|
||
public DatabaseType: string;
|
||
public BlockFile: BlockFileOf<DataUnit>;
|
||
public Data: FileArr<DataUnit>;
|
||
|
||
public constructor := exit;
|
||
|
||
public constructor(fname: string; version: byte; DatabaseType: string);
|
||
begin
|
||
self.version := version;
|
||
self.DatabaseType := DatabaseType;
|
||
|
||
self.BlockFile := new BlockFileOf<DataUnit>(fname);
|
||
self.BlockFile.Offset := HeaderSize;
|
||
self.BlockFile.Open(System.IO.FileMode.Create);
|
||
|
||
self.Data := new FileArr<DataUnit>(self.BlockFile);
|
||
end;
|
||
|
||
public class function Load(fname: string): Database;
|
||
begin
|
||
Result := new Database;
|
||
|
||
Result.BlockFile := new BlockFileOf<DataUnit>(fname, HeaderSize);
|
||
Result.BlockFile.Reset;
|
||
|
||
begin // заголовок
|
||
var br := new System.IO.BinaryReader(Result.BlockFile.BaseStream);
|
||
|
||
Result.version := br.ReadByte;
|
||
Result.DatabaseType := string.Create(ArrGen(4,i->char(br.ReadUInt16))).TrimEnd('_');
|
||
|
||
end;
|
||
|
||
Result.Data := new FileArr<DataUnit>(Result.BlockFile);
|
||
|
||
Result.Data.AutoFlush := false;
|
||
Result.Data.DeleteOnExit := false;
|
||
|
||
end;
|
||
|
||
public procedure Save;
|
||
begin
|
||
|
||
begin // заголовок
|
||
self.BlockFile.PosByte := 0;
|
||
var bw := new System.IO.BinaryWriter(self.BlockFile.BaseStream);
|
||
|
||
bw.Write(self.version);
|
||
|
||
foreach var ch in self.DatabaseType.PadRight(4,'_') do
|
||
bw.Write(word(ch));
|
||
|
||
end;
|
||
|
||
self.Data.Flush;
|
||
end;
|
||
|
||
public function ToString:string; override;
|
||
begin
|
||
var sb := new StringBuilder;
|
||
sb += $'version: {version}{#10}';
|
||
sb += $'Database Type: {DatabaseType}{#10}';
|
||
sb += $'Data:[{#10}';
|
||
|
||
BlockFile.Pos := 0;
|
||
foreach var du in BlockFile.Read(BlockFile.Size) do
|
||
sb += $'{#9}{du}{#10}';
|
||
|
||
sb += $']';
|
||
Result := sb.ToString;
|
||
end;
|
||
|
||
public procedure Dispose;
|
||
begin
|
||
Data.Finalize;
|
||
end;
|
||
|
||
end;
|
||
|
||
begin
|
||
var db1 := new Database('temp.bin',1,'JSBD'); // JSBD = Just Some Basic Data
|
||
|
||
db1.version := 5; // Была 1, изменили на 5
|
||
db1.Data[0] := new DataUnit(new DataType1(123));
|
||
db1.Data[1] := new DataUnit(new DataType2(456));
|
||
db1.Data[2] := new DataUnit(new DataType3('A'));
|
||
db1.Data[3] := new DataUnit(new DataType4(123.456));
|
||
|
||
db1.Save;
|
||
db1.Dispose;
|
||
|
||
|
||
|
||
db1 := Database.Load('temp.bin');
|
||
writeln(db1);
|
||
|
||
writeln;
|
||
// Пытаемся прочитать переменную типа integer там - где записана переменная типа real
|
||
// Это, конечно, выводит мусор
|
||
writeln('int from real: ', db1.Data[3].DataT2.i);
|
||
|
||
end. |