update
This commit is contained in:
parent
e264fce468
commit
88728016a9
|
|
@ -1,171 +1,174 @@
|
|||
///Модуль с массивом, хранящим данные в файле
|
||||
unit FileArray;
|
||||
|
||||
interface
|
||||
|
||||
uses BlockFileOfT;
|
||||
|
||||
type
|
||||
///Массив, хранящим данные в файле
|
||||
///На много медленнее обычного, но хранит на много больший объём данных
|
||||
///T может быть только запись, не содержащей ссылочные поля (указатели, классы и т.п.)
|
||||
FileArr<T> = sealed class
|
||||
where T: record;
|
||||
|
||||
private f: BlockFileOf<T>;
|
||||
///Автоматически отчищает кэш после каждого чтения и записи
|
||||
public AutoFlush := true;
|
||||
///Указывает, удалять ли файл когда сборщик мусора отчистит данную переменную
|
||||
public DeleteOnExit: boolean;
|
||||
|
||||
|
||||
private function GetItem32(i:integer):T;
|
||||
private function GetItem64(i:int64):T;
|
||||
private function GetSection32(from,c:integer):array of T;
|
||||
private function GetSection64(from,c:int64):array of T;
|
||||
private function GetSectionLazy32(from,c:integer):sequence of T;
|
||||
private function GetSectionLazy64(from,c:int64):sequence of T;
|
||||
|
||||
private procedure SetItem32(i:integer; o:T);
|
||||
private procedure SetItem64(i:int64; o:T);
|
||||
private procedure SetSection32(from,c:integer; o:array of T);
|
||||
private procedure SetSection64(from,c:int64; o:array of T);
|
||||
private procedure SetSectionLazy32(from,c:integer; o:sequence of T);
|
||||
private procedure SetSectionLazy64(from,c:int64; o:sequence of T);
|
||||
|
||||
|
||||
|
||||
///Количество элементов. В отличии от array of T, его можно устанавливать
|
||||
public property InnerFile: BlockFileOf<T> read f;
|
||||
|
||||
///Количество элементов. В отличии от array of T, его можно устанавливать
|
||||
public property Length: integer read f.FileSize write f.FileSize := value;
|
||||
///Количество элементов. В отличии от array of T, его можно устанавливать
|
||||
public property Length64: int64 read f.FileSize write f.FileSize := value;
|
||||
|
||||
///Элемент с индексом i
|
||||
public property Item[i:integer]:T read GetItem32 write SetItem32;
|
||||
///Элемент с индексом i
|
||||
public property Item64[i:int64]:T read GetItem64 write SetItem64; default;
|
||||
|
||||
///Срез начиная с элемента #from и длиной count
|
||||
public property Section[from,count:integer]:array of T read GetSection32 write SetSection32;
|
||||
///Срез начиная с элемента #from и длиной count
|
||||
public property Section64[from,count:int64]:array of T read GetSection64 write SetSection64;
|
||||
|
||||
///Возвращает ленивую последовательность начиная с элемента #from и длиной count
|
||||
///Устанавливает значение элементов начиная с элемента #from и длиной count
|
||||
public property SectionLazy[from,count:integer]:sequence of T read GetSectionLazy32 write SetSectionLazy32;
|
||||
///Возвращает ленивую последовательность начиная с элемента #from и длиной count
|
||||
///Устанавливает значение элементов начиная с элемента #from и длиной count
|
||||
public property SectionLazy64[from,count:int64]:sequence of T read GetSectionLazy64 write SetSectionLazy64;
|
||||
|
||||
|
||||
///Отчищает кэш чтения/записи
|
||||
procedure Flush := f.BaseStream.Flush;
|
||||
|
||||
|
||||
///Создаёт новый массив хранящий данные в файле fname. Если его нет - создаёт новый
|
||||
public constructor(fname: string) :=
|
||||
Create(fname, System.IO.FileMode.OpenOrCreate);
|
||||
///Создаёт новый массив хранящий данные в файле fname.
|
||||
///mode указывает каким образом открывать/создавать файл и имеет тип System.IO.FileMode
|
||||
public constructor(fname: string; mode: System.IO.FileMode);
|
||||
begin
|
||||
f := new BlockFileOf<T>(fname);
|
||||
f.Open(mode);
|
||||
end;
|
||||
///Использует заданный файл как основу для массива
|
||||
///Если файл не открыт - его откроет в режиме System.IO.FileMode.OpenOrCreate
|
||||
public constructor(&file: BlockFileOf<T>);
|
||||
begin
|
||||
f := &file;
|
||||
if not f.Opened then
|
||||
f.Open(System.IO.FileMode.OpenOrCreate);
|
||||
end;
|
||||
///Открывает заданный файл в режиме mode и использует его как основу для массива
|
||||
public constructor(&file: BlockFileOf<T>; mode: System.IO.FileMode);
|
||||
begin
|
||||
f := &file;
|
||||
f.Open(mode);
|
||||
end;
|
||||
///Создаёт новый массив хранящий данные в новом временном файл (в системной папке)
|
||||
///Созданный файл будет удалёт в произвольное время (после завершения пользования переменной) или при вызове Finalize
|
||||
public constructor;
|
||||
begin
|
||||
Create(System.IO.Path.GetTempFileName, System.IO.FileMode.CreateNew);
|
||||
DeleteOnExit := true;
|
||||
end;
|
||||
///Выполняет необходимую отчистку при завершении работы с переменной
|
||||
///Этот метот выполнится автоматически в произвольное время (после завершения пользования переменной)
|
||||
///Если попытаться использовать переменную после вызова этого метода - поведение будет неопределённым (скорее всего System.NullReferenceException)
|
||||
public procedure Finalize; override;
|
||||
begin
|
||||
f.Close;
|
||||
if DeleteOnExit then f.Erase;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{$region index property's}
|
||||
|
||||
function FileArr<T>.GetItem32(i:integer) := GetItem64(i);
|
||||
|
||||
function FileArr<T>.GetSection32(from,c:integer) := GetSection64(from,c);
|
||||
|
||||
function FileArr<T>.GetSectionLazy32(from,c:integer) := GetSectionLazy64(from,c);
|
||||
|
||||
procedure FileArr<T>.SetItem32(i:integer; o:T) := SetItem64(i,o);
|
||||
|
||||
procedure FileArr<T>.SetSection32(from,c:integer; o:array of T) := SetSection64(from,c,o);
|
||||
|
||||
procedure FileArr<T>.SetSectionLazy32(from,c:integer; o:sequence of T) := SetSectionLazy64(from,c,o);
|
||||
|
||||
function FileArr<T>.GetItem64(i:int64):T;
|
||||
begin
|
||||
f.Seek(i);
|
||||
Result := f.Read;
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
function FileArr<T>.GetSection64(from,c:int64):array of T;
|
||||
begin
|
||||
f.Seek(from);
|
||||
Result := f.Read(c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
function FileArr<T>.GetSectionLazy64(from,c:int64):sequence of T;
|
||||
begin
|
||||
f.Seek(from);
|
||||
Result := f.ReadLazy(c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetItem64(i:int64; o:T);
|
||||
begin
|
||||
f.Seek(i);
|
||||
f.Write(o);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetSection64(from,c:int64; o:array of T);
|
||||
begin
|
||||
f.Seek(from);
|
||||
if c > o.Length then raise new System.IndexOutOfRangeException('count должно быть <= длинны переданного массива');
|
||||
f.Write(o,0,c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetSectionLazy64(from,c:int64; o:sequence of T);
|
||||
begin
|
||||
f.Seek(from);
|
||||
f.Write(o,0,c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
{$endregion index property's}
|
||||
|
||||
///Модуль с массивом, хранящим данные в файле
|
||||
unit FileArray;
|
||||
|
||||
interface
|
||||
|
||||
uses BlockFileOfT;
|
||||
|
||||
type
|
||||
/// Массив, хранящий данные в файле
|
||||
/// Намного медленнее обычного массива, однако может хранить бОльший объём данных
|
||||
/// T может быть только записью, не содержащей ссылочные поля (указатели, классы и т.п.)
|
||||
FileArr<T> = sealed class
|
||||
where T: record;
|
||||
|
||||
private f: BlockFileOf<T>;
|
||||
///Автоматически очищает кэш после каждого чтения и записи
|
||||
public AutoFlush := true;
|
||||
///Указывает, удалять ли файл, когда сборщик мусора удалит данный объект
|
||||
public DeleteOnExit: boolean;
|
||||
|
||||
|
||||
private function GetItem32(i: integer): T;
|
||||
private function GetItem64(i: int64): T;
|
||||
private function GetSection32(from, c: integer): array of T;
|
||||
private function GetSection64(from, c: int64): array of T;
|
||||
private function GetSectionLazy32(from, c: integer): sequence of T;
|
||||
private function GetSectionLazy64(from, c: int64): sequence of T;
|
||||
|
||||
private procedure SetItem32(i: integer; o: T);
|
||||
private procedure SetItem64(i: int64; o: T);
|
||||
private procedure SetSection32(from, c: integer; o: array of T);
|
||||
private procedure SetSection64(from, c: int64; o: array of T);
|
||||
private procedure SetSectionLazy32(from, c: integer; o: sequence of T);
|
||||
private procedure SetSectionLazy64(from, c: int64; o: sequence of T);
|
||||
|
||||
|
||||
|
||||
public property InnerFile: BlockFileOf<T> read f;
|
||||
|
||||
///Возвращает количество элементов. В отличии от array of T - длину можно устанавливать
|
||||
public property Length: int64 read f.Size write f.Size := value;
|
||||
|
||||
///Возвращает или задаёт элемент с индексом i
|
||||
public property Item[i: integer]: T read GetItem32 write SetItem32;
|
||||
///Возвращает или задаёт элемент с индексом i
|
||||
public property Item64[i: int64]: T read GetItem64 write SetItem64; default;
|
||||
|
||||
///Срез начиная с элемента from и длиной count
|
||||
public property Section[from, count: integer]: array of T read GetSection32 write SetSection32;
|
||||
///Срез начиная с элемента from и длиной count
|
||||
public property Section64[from, count: int64]: array of T read GetSection64 write SetSection64;
|
||||
|
||||
///Возвращает ленивую последовательность начиная с элемента from и длиной count
|
||||
///Устанавливает значение элементов начиная с элемента from и длиной count
|
||||
public property SectionLazy[from, count: integer]: sequence of T read GetSectionLazy32 write SetSectionLazy32;
|
||||
///Возвращает ленивую последовательность начиная с элемента from и длиной count
|
||||
///Устанавливает значение элементов начиная с элемента from и длиной count
|
||||
public property SectionLazy64[from, count: int64]: sequence of T read GetSectionLazy64 write SetSectionLazy64;
|
||||
|
||||
|
||||
|
||||
///Очищает кэш чтения/записи
|
||||
public procedure Flush := f.BaseStream.Flush;
|
||||
|
||||
|
||||
///Создаёт новый массив, хранящий данные в файле fname. Если его нет - создаёт новый
|
||||
public constructor(fname: string) :=
|
||||
Create(fname, System.IO.FileMode.OpenOrCreate);
|
||||
|
||||
///Создаёт новый массив хранящий данные в файле fname.
|
||||
///mode указывает каким образом открывать/создавать файл и имеет тип System.IO.FileMode
|
||||
public constructor(fname: string; mode: System.IO.FileMode);
|
||||
begin
|
||||
f := new BlockFileOf<T>(fname);
|
||||
f.Open(mode);
|
||||
end;
|
||||
|
||||
///Использует заданный файл как основу для массива
|
||||
///Если файл не открыт - его откроет в режиме System.IO.FileMode.OpenOrCreate
|
||||
public constructor(&file: BlockFileOf<T>);
|
||||
begin
|
||||
f := &file;
|
||||
if not f.Opened then
|
||||
f.Open(System.IO.FileMode.OpenOrCreate);
|
||||
end;
|
||||
|
||||
///Открывает заданный файл в режиме mode и использует его как основу для массива
|
||||
public constructor(&file: BlockFileOf<T>; mode: System.IO.FileMode);
|
||||
begin
|
||||
f := &file;
|
||||
f.Open(mode);
|
||||
end;
|
||||
|
||||
/// Создаёт новый массив хранящий данные в новом временном файл (в системной папке)
|
||||
/// Созданный файл будет удалён в произвольное время после окончания использования переменной или при вызове Finalize
|
||||
public constructor;
|
||||
begin
|
||||
Create(System.IO.Path.GetTempFileName, System.IO.FileMode.CreateNew);
|
||||
DeleteOnExit := true;
|
||||
end;
|
||||
|
||||
///Выполняет необходимую очистку при завершении работы с переменной
|
||||
///Этот метод выполнится автоматически в произвольное время (после окончания использования переменной)
|
||||
///Если попытаться использовать переменную после вызова этого метода - поведение будет неопределённым
|
||||
public procedure Finalize; override;
|
||||
begin
|
||||
f.Close;
|
||||
if DeleteOnExit then f.Delete;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{$region index property's}
|
||||
|
||||
function FileArr<T>.GetItem32(i: integer) := GetItem64(i);
|
||||
|
||||
function FileArr<T>.GetSection32(from, c: integer) := GetSection64(from, c);
|
||||
|
||||
function FileArr<T>.GetSectionLazy32(from, c: integer) := GetSectionLazy64(from, c);
|
||||
|
||||
procedure FileArr<T>.SetItem32(i: integer; o: T) := SetItem64(i, o);
|
||||
|
||||
procedure FileArr<T>.SetSection32(from, c: integer; o: array of T) := SetSection64(from, c, o);
|
||||
|
||||
procedure FileArr<T>.SetSectionLazy32(from, c: integer; o: sequence of T) := SetSectionLazy64(from, c, o);
|
||||
|
||||
function FileArr<T>.GetItem64(i: int64): T;
|
||||
begin
|
||||
f.Seek(i);
|
||||
Result := f.Read;
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
function FileArr<T>.GetSection64(from, c: int64): array of T;
|
||||
begin
|
||||
f.Seek(from);
|
||||
Result := f.Read(c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
function FileArr<T>.GetSectionLazy64(from, c: int64): sequence of T;
|
||||
begin
|
||||
f.Seek(from);
|
||||
Result := f.ReadLazy(c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetItem64(i: int64; o: T);
|
||||
begin
|
||||
f.Seek(i);
|
||||
f.Write(o);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetSection64(from, c: int64; o: array of T);
|
||||
begin
|
||||
f.Seek(from);
|
||||
if c > o.Length then raise new System.IndexOutOfRangeException('count должно быть <= длинны переданного массива');
|
||||
f.Write(o, 0, c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
procedure FileArr<T>.SetSectionLazy64(from, c: int64; o: sequence of T);
|
||||
begin
|
||||
f.Seek(from);
|
||||
f.Write(o, 0, c);
|
||||
if AutoFlush then f.BaseStream.Flush;
|
||||
end;
|
||||
|
||||
{$endregion index property's}
|
||||
|
||||
end.
|
||||
|
|
@ -1,203 +1,201 @@
|
|||
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, Size=16)]//Значит - мы сами указываем куда ставит каждое поле
|
||||
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)
|
||||
|
||||
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 := 5;
|
||||
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,5);
|
||||
Result.BlockFile.Reset;
|
||||
|
||||
begin//заголовок
|
||||
var str := Result.BlockFile.BaseStream;
|
||||
var sr := new System.IO.StreamReader(Result.BlockFile.BaseStream, System.Text.Encoding.UTF8);
|
||||
|
||||
//str.Position := 0;//Мы только что открыли файл, Reset само поставило указатель в начало
|
||||
|
||||
Result.version := str.ReadByte;
|
||||
|
||||
var buff := new char[4];
|
||||
sr.ReadBlock(buff,0,4);
|
||||
Result.DatabaseType := new string(buff);
|
||||
|
||||
end;
|
||||
|
||||
Result.Data := new FileArr<DataUnit>(Result.BlockFile);
|
||||
|
||||
Result.Data.AutoFlush := false;
|
||||
Result.Data.DeleteOnExit := false;
|
||||
|
||||
end;
|
||||
|
||||
public procedure Save;
|
||||
begin
|
||||
|
||||
begin//заголовок
|
||||
var str := self.BlockFile.BaseStream;
|
||||
var sw := new System.IO.StreamWriter(str, System.Text.Encoding.UTF8);
|
||||
|
||||
str.Position := 0;
|
||||
|
||||
str.WriteByte(self.version);
|
||||
|
||||
var buff := (self.DatabaseType+'_'*4).ToCharArray;
|
||||
sw.Write(buff, 0,4);
|
||||
|
||||
sw.Flush;
|
||||
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;
|
||||
writeln('int from real: ',db1.Data[3].DataT2.i);//Пытаемся прочитать переменную типа integer там - где записана переменная типа real
|
||||
//Это, конечно, выводит мусор
|
||||
|
||||
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.
|
||||
|
|
@ -1,48 +1,79 @@
|
|||
uses BlockFileOfT;
|
||||
|
||||
type
|
||||
r1=record
|
||||
b1,b2:byte;
|
||||
|
||||
constructor(b1,b2:byte);
|
||||
begin
|
||||
self.b1 := b1;
|
||||
self.b2 := b2;
|
||||
end;
|
||||
///Переопределение того - как объекты типа r1 будет выписывать writeln
|
||||
function ToString:string; override :=
|
||||
$'r1({b1}, {b2})';
|
||||
end;
|
||||
r2=record
|
||||
b:byte;
|
||||
i:int64;
|
||||
end;
|
||||
|
||||
begin
|
||||
var f := new BlockFileOf<r1>('temp.bin');
|
||||
f.Rewrite;
|
||||
|
||||
f.Write(new r1(1,2));
|
||||
f.Write(new r1(3,4));
|
||||
f.Write(new r1(5,6));
|
||||
|
||||
f.Pos := 1;
|
||||
writeln(f.BinReader.ReadByte);//3 - прочитало первый байт из второго элемента
|
||||
writeln(f.Read);//А вот так делать не надо. Сейчас курсор находится в середите второго элемента
|
||||
//Обычно это вызовет неопределённое поведение и заполнит поля полученной записи мусором
|
||||
//Но этот случай простой, поэтому прочитает половину второй + половину третьей записи и выведет (4,5)
|
||||
//Если используете BinReader, BinWriter или BaseStream - надо удостоверится что всё привели в порядок
|
||||
//К примеру, тут сработает "f.Pos := n" после строчки с "f.BinReader"
|
||||
|
||||
f.Pos := 0;
|
||||
f.PosByte += 1;//сдвигаем курсор на 1 байт (вся запись f.TSize, что, в данном случае 2)//но это не у всех сработает, почему - в следующем абзаце
|
||||
writeln(f.BinReader.ReadByte);//2, потому что второй байт первой записи
|
||||
|
||||
f.Close;
|
||||
|
||||
var f2 := new BlockFileOf<r2>;
|
||||
writeln(f2.TSize);//То же самое что sizeof(r2). У меня 16, хотя byte=1 + int64=8, то есть должно быть 9?
|
||||
//Это потому, что на разных процессорах добавляет свои отступы (так легче читать/записывать) в некоторые записи, так что будьте осторожны
|
||||
//Это так же хорошая причина не пользоваться бездумно BinReader, BinWriter или BaseStream - потому что на разных компьютерах может быть по разному
|
||||
|
||||
uses BlockFileOfT;
|
||||
|
||||
uses System.Runtime.InteropServices; // для StructLayout у r3
|
||||
|
||||
type
|
||||
r1 = record
|
||||
b1, b2: byte;
|
||||
|
||||
constructor(b1, b2: byte);
|
||||
begin
|
||||
self.b1 := b1;
|
||||
self.b2 := b2;
|
||||
end;
|
||||
|
||||
///Переопределение того - как объекты типа r1 будет выписывать writeln
|
||||
function ToString: string; override :=
|
||||
$'r1({b1}, {b2})';
|
||||
|
||||
end;
|
||||
|
||||
r2 = record
|
||||
b: byte;
|
||||
i: int64;
|
||||
end;
|
||||
|
||||
[StructLayout(LayoutKind.&Explicit, Size=9)]
|
||||
r3 = record
|
||||
[FieldOffset(0)] b: byte;
|
||||
// Явно указываем что i будет хранится на следующем байте после b
|
||||
// Так можно даже накладывать поля друг на друга
|
||||
// Но поэтому также нужно относиться с осторожностью к такой возможности
|
||||
// И помнить у какого типа какой размер, чтобы наложение полей не получилось там, где оно не требуется
|
||||
[FieldOffset(1)] i: int64;
|
||||
end;
|
||||
|
||||
begin
|
||||
var f := new BlockFileOf<r1>('temp.bin');
|
||||
f.Rewrite;
|
||||
|
||||
f.Write(new r1(1, 2));
|
||||
f.Write(new r1(3, 4));
|
||||
f.Write(new r1(5, 6));
|
||||
|
||||
f.Pos := 1;
|
||||
var str := f.BaseStream;
|
||||
var br := new System.IO.BinaryReader(str);
|
||||
Writeln(br.ReadByte); // 3 - потому что прочитало 1 байт, когда файловый курсор стоял в начале элемента #1
|
||||
|
||||
// А вот так делать не следует. Сейчас курсор находится в середине второго элемента
|
||||
// Обычно это вызовет неопределённое поведение и заполнит поля полученной записи мусором
|
||||
// Но этот случай простой, поэтому точно известно что прочитает половину второй и половину третьей записи и выведет (4,5)
|
||||
Writeln(f.Read);
|
||||
// Если вы НЕ пытаетесь специально читать мимо элементов - стоит устанавливать позицию в файле (f.Pos := ...) после прямой работы с BaseStream
|
||||
|
||||
f.Pos := 0;
|
||||
f.PosByte += 1; // сдвигаем курсор на один байт (вся запись f.TSize байт, что, в данном случае, 2)
|
||||
// br и f всё ещё работают над тем же потоком, потому что мы не закрывали файл
|
||||
// А файловый курсор хранится как раз в потоке
|
||||
// Поэтому br можно всё ещё использовать и он будет синхронизирован с f.PosByte
|
||||
Writeln(br.ReadByte); // 2, потому что второй байт первой записи
|
||||
|
||||
f.Close;
|
||||
|
||||
|
||||
|
||||
|
||||
var f2 := new BlockFileOf<r2>;
|
||||
// f2.TSize это то же самое, что sizeof(r2)
|
||||
// У меня 16, хотя sizeof(byte)=1 + sizeof(int64)=8, то есть должно быть 9?
|
||||
// Это потому, что на многих процессорах добавляет отступы (так легче читать/записывать значения) в некоторые записи, так что будьте осторожны
|
||||
// Это также хорошая причина не пользоваться бездумно BaseStream - потому что на разных компьютерах один и тот же код может работать по-разному
|
||||
Writeln(f2.TSize);
|
||||
|
||||
var f3 := new BlockFileOf<r3>;
|
||||
// А вот теперь размер 9, потому что мы явно указали что отступ нам не нужен
|
||||
// Конечно, теперь чтение/запись i может быть медленнее на некоторых процессорах, отступ всё же не для красоты был
|
||||
Writeln(f3.TSize);
|
||||
|
||||
end.
|
||||
|
|
@ -1,61 +1,67 @@
|
|||
uses BlockFileOfT;
|
||||
|
||||
type
|
||||
r1=record
|
||||
i:integer;
|
||||
r:real;
|
||||
|
||||
constructor(i:integer; r:real);
|
||||
begin
|
||||
self.i := i;
|
||||
self.r := r;
|
||||
end;
|
||||
///Переопределение того - как объекты типа r1 будет выписывать writeln
|
||||
function ToString:string; override :=
|
||||
$'r1({i}, {r})';
|
||||
end;
|
||||
|
||||
begin
|
||||
//var f: BlockFileOf<r1>;
|
||||
//f := new BlockFileOf<r1>;//В отличии от file of T - всегда надо инициализировать перед тем как использовать
|
||||
//f.Assign('temp.bin');
|
||||
var f := new BlockFileOf<r1>('temp.bin');//эта строчка заменяет предыдущие 3
|
||||
|
||||
f.Rewrite;//Если навести мышку на метод (в данном случае Rewrite) - показывает описание
|
||||
f.Write(new r1(123,123.456));
|
||||
f.Write(new r1(456,456.789));
|
||||
//f.Flush;//не обязательно - Close делает то же самое перед тем как закрыть файл
|
||||
f.Close;//После работы с файлом - надо его закрыть.
|
||||
|
||||
//f.Assign('temp.bin');//Не обязательно, файл остаётся привязан после закрытия
|
||||
|
||||
f.Reset;
|
||||
writeln($'Read#1: {f.Read}');//r1(123,123.456)
|
||||
|
||||
writeln($'Pos#1: {f.Pos}');//1, потому что мы открыли файл через "Reset" (курсор вначале файла) и затем прочитали 1 элемент
|
||||
|
||||
f.Write(new r1(0,0));//В отличии от file of T, у BlockFileOf<T> нету режима чтения и режима записи
|
||||
//Они объеденены, поэтому не надо закрывать файл чтоб записать что то после чтения
|
||||
|
||||
writeln($'Size#1: {f.Size}');//2, потому что r1(0,0) не втиснуло между первыми 2 элементами, а записало поверх второго
|
||||
|
||||
writeln($'EOF: {f.EOF}');//True, потому что у нас всего было 2 элемента, и курсор сейчас стоит на позиции 2 (1 чтение + 1 запись)
|
||||
|
||||
//f.Seek(1);
|
||||
f.Pos := 1;//то же самое что и Seek(1)
|
||||
|
||||
writeln($'Read#2: {f.Read}');//r1(0,0), то, что мы только что записали
|
||||
|
||||
f.Pos := 5;//Можно ставить за границей файла
|
||||
//f.Read;//Ошибка: Нельзя читать за пределами файла. Можно только записывать
|
||||
f.Write(new r1);
|
||||
writeln($'Size#2: {f.Size}');//6, потому что мы записали +1 элемент после позиции 5
|
||||
|
||||
f.Size := 10;//Можно и так, в этом случае не надо ничего записывать
|
||||
writeln($'Size#3: {f.Size}');//10
|
||||
writeln($'Pos#2: {f.Pos}');//6, как и была после того как мы последний раз вызвали f.Write
|
||||
|
||||
f.Flush;//Программа сейчас завершится, поэтому файл закроется сам
|
||||
//Но если не вызвать хотя бы Flush - изменения могут не сохранится
|
||||
|
||||
uses BlockFileOfT;
|
||||
|
||||
type
|
||||
r1=record
|
||||
i:integer;
|
||||
r:real;
|
||||
|
||||
constructor(i:integer; r:real);
|
||||
begin
|
||||
self.i := i;
|
||||
self.r := r;
|
||||
end;
|
||||
///Переопределение того - как объекты типа r1 будет выписывать writeln
|
||||
function ToString:string; override :=
|
||||
$'r1({i}, {r})';
|
||||
end;
|
||||
|
||||
begin
|
||||
// var f: BlockFileOf<r1>;
|
||||
// f := new BlockFileOf<r1>; // В отличии от file of T - BlockFileOf<T> всегда надо инициализировать перед тем как использовать
|
||||
// f.Assign('temp.bin');
|
||||
var f := new BlockFileOf<r1>('temp.bin'); // эта строчка работает как предыдущие 3
|
||||
|
||||
f.Rewrite; // Если навести мышку на метод (в данном случае .Rewrite) - показывает описание
|
||||
f.Write(new r1(123,123.456));
|
||||
f.Write(new r1(456,456.789));
|
||||
|
||||
// f.Flush; // не обязательно - .Close вызывает .Flush перед тем как закрыть файл
|
||||
// После работы с файлом - надо его закрыть. Иначе:
|
||||
// 1. Часть данных может не сохраниться
|
||||
// 2. Изменять файл вне переменной f будет нельзя, пока f держит его открытым
|
||||
f.Close;
|
||||
|
||||
// f.Assign('temp.bin'); // Не обязательно, файл остаётся привязан после закрытия
|
||||
|
||||
f.Reset;
|
||||
writeln($'Read#1: {f.Read}'); // r1(123,123.456)
|
||||
writeln($'Pos#1: {f.Pos}'); // 1, потому что мы открыли файл через ".Reset" (курсор в начале файла) и затем прочитали 1 элемент
|
||||
|
||||
// В отличии от file of T, у BlockFileOf<T> нету режима чтения и режима записи
|
||||
// Они объеденены, поэтому не надо закрывать файл чтобы записать что-то после чтения
|
||||
f.Write(new r1(0,0));
|
||||
|
||||
writeln($'Size#1: {f.Size}'); // 2, потому что r1(0,0) НЕ втиснуло между первыми 2 элементами, а записало поверх второго
|
||||
|
||||
writeln($'EOF: {f.EOF}'); // True, потому что у нас всего было 2 элемента и курсор сейчас стоит на позиции 2 (1 чтение + 1 запись)
|
||||
|
||||
// f.Seek(1);
|
||||
f.Pos := 1; // То же самое, что и .Seek(1), но лучше
|
||||
|
||||
writeln($'Read#2: {f.Read}'); // r1(0,0), то, что мы только что записали
|
||||
|
||||
f.Pos := 5; // Можно ставить за границей файла
|
||||
// f.Read; // Ошибка: Нельзя читать за пределами файла. Можно только записывать
|
||||
f.Write(new r1);
|
||||
writeln($'Size#2: {f.Size}'); // 6, потому что мы записали +1 элемент после позиции 5
|
||||
|
||||
// Но вообще, размер правильно менять так:
|
||||
f.Size := 10;
|
||||
writeln($'Size#3: {f.Size}'); // 10
|
||||
writeln($'Pos#2: {f.Pos}'); // 6, как и была после того, как мы последний раз вызвали .Write
|
||||
|
||||
// Программа сейчас завершится, поэтому файл закроется сам
|
||||
// Но если не вызвать хотя бы Flush, то изменения могут не сохранится
|
||||
f.Flush;
|
||||
|
||||
end.
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3">Выберете раздел:</font>
|
||||
<ul>
|
||||
<li><font size="4"><a href="СправкаData/main.html">Основы</a></font></li>
|
||||
<li><font size="4"><a href="СправкаData/WhyOffset.html">Для чего нужен Offset</a></font></li>
|
||||
<li><font size="4"><a href="СправкаData/RefTSaving.html">Сохранение ссылочных и особых типов</a></font></li>
|
||||
</ul>
|
||||
<br>
|
||||
Если найдёте что то неправильное/ошибку (в том числе грамматическую) в справке - пишите <a href=https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/issues>сюда</a>.
|
||||
</body>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3">Выберете раздел:</font>
|
||||
<ul>
|
||||
<li><font size="4"><a href="СправкаData/main.html">Основы</a></font></li>
|
||||
<li><font size="4"><a href="СправкаData/WhyOffset.html">Для чего нужен Offset</a></font></li>
|
||||
<li><font size="4"><a href="СправкаData/RefTSaving.html">Сохранение ссылочных типов</a></font></li>
|
||||
</ul>
|
||||
<br>
|
||||
Если найдёте что-то неправильное или ошибку (в том числе и грамматическую) в справке - пишите <a href=https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/issues>сюда</a>.
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,491 +1,134 @@
|
|||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
<font size="5">Сохранение ссылочных и особых типов в блочный файл.</font><br>
|
||||
<hr />
|
||||
|
||||
|
||||
<p onClick="toggle_show('spet')"><font size="4" color="008000" style="cursor: pointer">В чём проблема особых типов</font></p>
|
||||
<div id=spet style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">r1=record </t>
|
||||
<br><t style="margin-left: 20px">ch: char; </t>
|
||||
<br><t style="margin-left: 20px">constructor(ch: char) := </t>
|
||||
<br><t style="margin-left: 20px">self.ch := ch; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write(new r1('a')); </t>
|
||||
<br><t style="margin-left: 10px">f.Write(new r1('b'),new r1('c'));//Ошибка: Объект содержит данные, которые не являются примитивами или требуют преобразования. </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Тут второе Write вызовет ошибку описаную выше.<br>
|
||||
Это потому - что во второй Write передаётся array of r1.<br>
|
||||
Когда для копирования памяти из этого массива в массив байт запрашивается блокировка памяти -<br>
|
||||
.Net видит что r1 содержит поле типа - char. Я так и не нашёл однозначного определения что с ним не так.<br>
|
||||
Но, вроде, проблема в том - что в разных системах и версиях char может по разному хранится в памяти.<br>
|
||||
И эта ошибка - защита от дурака, чтоб не дать данные из 1 системы скопировать в другую.<br>
|
||||
<br>
|
||||
char - не единственный такой "особый тип"<br>
|
||||
если получаете такую же ошибку - записывайте и считывайте по 1 элементу<br>
|
||||
или воспользуйтесь следующим способом обхода:<br>
|
||||
<br>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px">uses System.Runtime.InteropServices; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">[StructLayout(LayoutKind.&Explicit)] </t>
|
||||
<br><t style="margin-left: 10px">CWConv = record </t>
|
||||
<br><t style="margin-left: 20px">[FieldOffset(0)] ch:char;//У обоих этих полей одинаковая позиция в памяти (0 байт от начала записси) и объём (2 байт) </t>
|
||||
<br><t style="margin-left: 20px">[FieldOffset(0)] id:word;//Поэтому можно записать в 1 поле, а считать из другого, не теряя информацию </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">r1=record </t>
|
||||
<br><t style="margin-left: 20px">ch: word; </t>
|
||||
<br><t style="margin-left: 20px">constructor(ch: char) := </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">var a: CWConv; </t>
|
||||
<br><t style="margin-left: 30px">a.ch := ch; </t>
|
||||
<br><t style="margin-left: 30px">self.ch := a.id; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 20px">function GetCh:char; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">var a: CWConv; </t>
|
||||
<br><t style="margin-left: 30px">a.id := self.ch; </t>
|
||||
<br><t style="margin-left: 30px">Result := a.ch; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write(new r1('a')); </t>
|
||||
<br><t style="margin-left: 10px">f.Write(new r1('b'),new r1('c')); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Тип word занимает в памяти 2 байта, так же как char, поэтому char можно заменить на word.<br>
|
||||
Узнать объём типа можно вызвав sizeof(word), sizeof(char) и т.д.<br>
|
||||
Конечно, в данном случае создавать CWConv не обязательно, потому что word(ch) и char(id) и без того работает.<br>
|
||||
Но с другими особыми типами это может не сработать.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">В чём проблема ссылочных типов</font></p>
|
||||
<div id=decl style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">r1=record </t>
|
||||
<br><t style="margin-left: 20px">b:byte; </t>
|
||||
<br><t style="margin-left: 20px">i:integer; </t>
|
||||
<br><t style="margin-left: 20px">s:string; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var a: r1; </t>
|
||||
<br><t style="margin-left: 10px">a.b := 5; </t>
|
||||
<br><t style="margin-left: 10px">a.i := 12345; </t>
|
||||
<br><t style="margin-left: 10px">a.s := 'abcdef'*10000;//Это 60 000 символов, ~120KB </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Проблема заключается в том, как хранится элементы этой записи в оперативной памяти.<br>
|
||||
"a.b" и "a.i" будут находится прямо друг за другом.<br>
|
||||
Но "a.s" будет где то в случайной области памяти.<br>
|
||||
При этом, после "a.i", вместо "a.s" будет номер ячейки памяти (по сути - ссылка, отсюда название), в которую сохранило "a.s".<br>
|
||||
Блочное сохранение обычно работает потому, что все элементы записи хранит друг за другом, 1 блоком в памяти.<br>
|
||||
Этот блок можно целиком скопировать в файл, что является главным принципом "BlockFileOf<T>".<br>
|
||||
Но раз "a.s" находится не в теле записи - вместо него в файл сохранит только номер ячейки где "a.s" хранится.<br>
|
||||
Если, потом, попытаться загрузить эту запись назад - в "a.s" запишет номер ячейки где раньше была наша строка.<br>
|
||||
Но строки по тому адресу уже может и не быть. Если так - попытка получить её значение выведет мусор или даст ошибку чтения защищённой памяти.<br>
|
||||
Кроме всего прочего, сборщик мусора от .Net может перемещать ссылочные типы в памяти, для экономии места.<br>
|
||||
Если запись ещё находится в оперативной памяти в момент сборки мусора - адрес строки не забудет поменять.<br>
|
||||
Но если запись сохранили в файл, потом произошла сборка мусора, а затем мы загрузили запись назад - адрес строки будет уже устаревший.<br>
|
||||
<br>
|
||||
В общем, сохранение записей с полями ссылочных типов - ни к чему хорошему не приведёт.<br>
|
||||
Но, все записи имеют неизменяемую длину, в то время как, к примеру, у массивов и строк - практически нету ограничения.<br>
|
||||
Это 1 из причин, почему ссылочные типы удобнее хранить отдельно, по ссылке.<br>
|
||||
И неизменяемая длина - не только ограничение записей, это так же ограничение алгоритмов "file of T" и "BlockFileOf<T>",<br>
|
||||
потому что для нормальной работы "Seek" - надо чтоб все элементы имели одинаковую длину.<br>
|
||||
По этому, в примерах сохранения ссылочных типов, описаных далее - всегда стоит определённое ограничение на рамер,<br>
|
||||
которое можно менять только перед компиляцией.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Массивы (статичные)</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px">uses System.Runtime.InteropServices; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">[StructLayout(LayoutKind.&Explicit)] </t>
|
||||
<br><t style="margin-left: 10px">CharArr15 = record </t>
|
||||
<br><t style="margin-left: 20px">private const MaxLength = 15;//Ограничение кол-ва элементов </t>
|
||||
<br><t style="margin-left: 20px">private const TSize = 2;//То что возвращает sizeof(T) где T - тип элементов массива (в нашем случае - char) </t>
|
||||
<br><t style="margin-left: 20px">private const Size = MaxLength * TSize; </t>
|
||||
<br><t style="margin-left: 20px">private [FieldOffset(Size-1)] last: byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(a: array of char): CharArr15; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">if a.Length < MaxLength then </t>
|
||||
<br><t style="margin-left: 40px">a := a + new char[MaxLength - a.Length];//Если не добавить тут лишние нули - копирование содержимого попытается прочитать после конца массива </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(a, GCHandleType.Pinned);//Надо заблокировать память, иначе сборщик мусора может очень невовремя переместить массив </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^CharArr15 := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">Result := ptr^;//Тут содержимое массива читает как CharArr15. Обычно так делать не стоит, но тут это лучший вариант, ибо единственный </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free;//Если возникнет какая то ошибка - надо всё равно освободить дескриптор, иначе будет утечка памяти </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(a: CharArr15): array of char; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">Result := new char[MaxLength]; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(Result, GCHandleType.Pinned); </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^CharArr15 := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">ptr^ := a;//А тут назад, содержимое "a" копируется в результат </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free; </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToRefArr: array of char; </t>
|
||||
<br><t style="margin-left: 20px">type </t>
|
||||
<br><t style="margin-left: 30px">CharArr = array of char; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">Result := CharArr(self); </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">r1 = record </t>
|
||||
<br><t style="margin-left: 20px">public a: CharArr15; </t>
|
||||
<br><t style="margin-left: 20px">public i: integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public constructor(a: array of char; i: integer); </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">self.a := CharArr15(a); </t>
|
||||
<br><t style="margin-left: 30px">self.i := i; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public constructor(s: string; i: integer) := </t>
|
||||
<br><t style="margin-left: 20px">Create(s.ToCharArray, i); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToString: string; override := </t>
|
||||
<br><t style="margin-left: 20px">//Replace(#0,#32) заменяет пустые символы на пробелы </t>
|
||||
<br><t style="margin-left: 20px">//IDE паскаля немного ломается, если попытаться вывести char(0) </t>
|
||||
<br><t style="margin-left: 20px">$'r1("{(new string(a.ToRefArr)).Replace(#0,#32)}", {i})'; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">var a1 := new r1(new char[]('a', 'b', 'd', 'q'), 6); </t>
|
||||
<br><t style="margin-left: 10px">var b1 := new r1('Hello, world!', 278); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">{ </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write(a1, b1); </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Pos := 0; </t>
|
||||
<br><t style="margin-left: 10px">var a2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">var b2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(a1.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b1.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(a2.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b2.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Что делают "[StructLayout(LayoutKind.&Explicit)]" и "[FieldOffset(Size-1)]"?<br>
|
||||
"FieldOffset" указывает где в записи будет размещено поле "last: byte"<br>
|
||||
Таким образом, мы, как бы, резервируем "Size" байт для записи.<br>
|
||||
Они оказываются ничем не заняты, но в них можно сохранить информацию, что далее и происходит.<br>
|
||||
А "LayoutKind.&Explicit" разрешает использовать "FieldOffset" для "CharArr15".<br>
|
||||
<br>
|
||||
"StructLayout", "LayoutKind.&Explicit" и "FieldOffset" находятся в пространстве имён<br>
|
||||
"System.Runtime.InteropServices", поэтому я подключил его в начале файла.<br>
|
||||
Но его можно не подключать, тогда придётся писать "System.Runtime.InteropServices.StructLayout" и т.п.<br>
|
||||
Хотя есть ещё третий вариант - добавить свои синонимы в начале файла:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">StructLayout = System.Runtime.InteropServices.StructLayoutAttribute; </t>
|
||||
<br><t style="margin-left: 10px">LayoutKind = System.Runtime.InteropServices.LayoutKind; </t>
|
||||
<br><t style="margin-left: 10px">FieldOffset = System.Runtime.InteropServices.FieldOffsetAttribute; </t>
|
||||
</p>
|
||||
Как видите, с тем что уже есть - можно хранить и строки, как массивы символов.<br>
|
||||
Но, хотя мы сохраняли массив из 4 элемента и строку на 13 символов - при чтении назад им дало длину 15.<br>
|
||||
Это вполне реально исправить, надо лишь сохранять - сколько символов было использовано.<br>
|
||||
Но, конечно, это будут дополнительные затраты памяти и производительности:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">StructLayout = System.Runtime.InteropServices.StructLayoutAttribute; </t>
|
||||
<br><t style="margin-left: 10px">LayoutKind = System.Runtime.InteropServices.LayoutKind; </t>
|
||||
<br><t style="margin-left: 10px">FieldOffset = System.Runtime.InteropServices.FieldOffsetAttribute; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">GCHandle = System.Runtime.InteropServices.GCHandle; </t>
|
||||
<br><t style="margin-left: 10px">GCHandleType = System.Runtime.InteropServices.GCHandleType; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">[StructLayout(LayoutKind.&Explicit)] </t>
|
||||
<br><t style="margin-left: 10px">CharArr15Body = record </t>
|
||||
<br><t style="margin-left: 20px">private const MaxLength = 15; </t>
|
||||
<br><t style="margin-left: 20px">private const TSize = 2; </t>
|
||||
<br><t style="margin-left: 20px">private const Size = MaxLength * TSize; </t>
|
||||
<br><t style="margin-left: 20px">[FieldOffset(Size-1)] last: byte; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">[StructLayout(LayoutKind.&Explicit)] </t>
|
||||
<br><t style="margin-left: 10px">CharArr15 = record </t>
|
||||
<br><t style="margin-left: 20px">//shortint занимает 1 байт и принимает значения от -128 до 127 </t>
|
||||
<br><t style="margin-left: 20px">//У нас максимум 15 элементов, поэтому этого вполне хватит. </t>
|
||||
<br><t style="margin-left: 20px">public [FieldOffset(0)] length: shortint; </t>
|
||||
<br><t style="margin-left: 20px">//Тут FieldOffset=1, потому что length занимает 1 байт в памяти </t>
|
||||
<br><t style="margin-left: 20px">//Если в вашей реализации "length" будет занимать бОльший объём - не забудьте поменять </t>
|
||||
<br><t style="margin-left: 20px">public [FieldOffset(1)] body: CharArr15Body; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(a: array of char): CharArr15; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">Result.length := a.Length;//Если передать массив с длиной больше чем помещается в shortint - произвойдёт переполнение. </t>
|
||||
<br><t style="margin-left: 30px">//Если такая возможность существует - надо поставить тут проверку, чтоб длине не присваивало ничего > 127 </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">if a.Length < CharArr15Body.MaxLength then </t>
|
||||
<br><t style="margin-left: 40px">a := a + new char[CharArr15Body.MaxLength - a.Length]; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(a, GCHandleType.Pinned); </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^CharArr15Body := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">Result.body := ptr^; </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free; </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(a: CharArr15): array of char; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">//Если кол-во сохранённых байт < MaxLength - a.body будет занимать бОльший объём в памяти, чем поместится в Result </t>
|
||||
<br><t style="margin-left: 30px">//Поэтому надо сначала прочитать всё, включая пустые символы на конце, во временную переменную </t>
|
||||
<br><t style="margin-left: 30px">//А затем скопировать нужное кол-во символов в Result </t>
|
||||
<br><t style="margin-left: 30px">var temp := new char[CharArr15Body.MaxLength]; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(temp, GCHandleType.Pinned); </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^CharArr15Body := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">ptr^ := a.body; </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free; </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 30px">Result := new char[a.length]; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 30px">//Если в "explicit(a:array of char):CharArr15" вы запретили length>MaxLength, </t>
|
||||
<br><t style="margin-left: 30px">//То "Min(a.length, CharArr15Body.MaxLength)" можно заменить на "a.length" </t>
|
||||
<br><t style="margin-left: 30px">System.Array.Copy(temp, Result, Min(a.length, CharArr15Body.MaxLength)); </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToRefArr: array of char; </t>
|
||||
<br><t style="margin-left: 20px">type </t>
|
||||
<br><t style="margin-left: 30px">CharArr = array of char; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">Result := CharArr(self); </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">r1 = record </t>
|
||||
<br><t style="margin-left: 20px">public a: CharArr15; </t>
|
||||
<br><t style="margin-left: 20px">public i: integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public constructor(a: array of char; i: integer); </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">self.a := CharArr15(a); </t>
|
||||
<br><t style="margin-left: 30px">self.i := i; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public constructor(s: string; i: integer) := </t>
|
||||
<br><t style="margin-left: 20px">Create(s.ToCharArray, i); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToString: string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'r1("{(new string(a.ToRefArr)).Replace(#0,#32)}", {i})'; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">var a1 := new r1(new char[]('a', 'b', 'd', 'q'), 6); </t>
|
||||
<br><t style="margin-left: 10px">var b1 := new r1('Hello, world!', 278); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">{ </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write(a1, b1); </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Pos := 0; </t>
|
||||
<br><t style="margin-left: 10px">var a2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">var b2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(a1.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b1.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(a2.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b2.a.ToRefArr.Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
В этот раз, уже, нулевые символы в конеце строки обрезало при чтении,<br>
|
||||
потому что знало после какого символа надо обрезать.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('to_seq')"><font size="4" color="008000" style="cursor: pointer">Использование сериализаторов</font></p>
|
||||
<div id=to_seq style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px">uses System.Runtime.InteropServices; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">[StructLayout(LayoutKind.&Explicit)] </t>
|
||||
<br><t style="margin-left: 10px">Str15 = record </t>
|
||||
<br><t style="margin-left: 20px">private const MaxLength = 15; </t>
|
||||
<br><t style="margin-left: 20px">//Я провёл много тестов чтоб убедится: </t>
|
||||
<br><t style="margin-left: 20px">//-BinaryFormatter всегда делает для string заголовок в 25 байт </t>
|
||||
<br><t style="margin-left: 20px">//-В разных случаях тело строки может сохранять как MaxLength*1, MaxLength*2 и MaxLength*3 байт. </t>
|
||||
<br><t style="margin-left: 20px">//-Предыдущее зависит от того, на сколько редкие символы были использованы. Для более редких символов надо больше памяти </t>
|
||||
<br><t style="margin-left: 20px">//Поэтому, на всякий случай, я сделал Size с расчётом на то, что все MaxLength символов могут быть редкими. </t>
|
||||
<br><t style="margin-left: 20px">private const Size = 25 + MaxLength * 3; </t>
|
||||
<br><t style="margin-left: 20px">private [FieldOffset(Size-1)] last: byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">private class f := new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(s: string): Str15; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">if s.Length > MaxLength then s := s.Substring(0, MaxLength); </t>
|
||||
<br><t style="margin-left: 30px">var str := new System.IO.MemoryStream(Size); </t>
|
||||
<br><t style="margin-left: 30px">str.SetLength(Size); </t>
|
||||
<br><t style="margin-left: 30px">f.Serialize(str, s); </t>
|
||||
<br><t style="margin-left: 30px">var a := str.ToArray; </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(a, GCHandleType.Pinned); </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^Str15 := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">Result := ptr^; </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free; </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public class function operator explicit(s: Str15): string; </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">var a := new byte[Size]; </t>
|
||||
<br><t style="margin-left: 30px">var hnd := GCHandle.Alloc(a, GCHandleType.Pinned); </t>
|
||||
<br><t style="margin-left: 30px">try </t>
|
||||
<br><t style="margin-left: 40px">var ptr: ^Str15 := pointer(hnd.AddrOfPinnedObject); </t>
|
||||
<br><t style="margin-left: 40px">ptr^ := s; </t>
|
||||
<br><t style="margin-left: 30px">finally </t>
|
||||
<br><t style="margin-left: 40px">hnd.Free; </t>
|
||||
<br><t style="margin-left: 30px">end; </t>
|
||||
<br><t style="margin-left: 30px">var str := new System.IO.MemoryStream(a); </t>
|
||||
<br><t style="margin-left: 30px">Result := string(f.Deserialize(str)); </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">r1 = record </t>
|
||||
<br><t style="margin-left: 20px">public s: Str15; </t>
|
||||
<br><t style="margin-left: 20px">public i: integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public constructor(s: string; i: integer); </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">self.s := Str15(s); </t>
|
||||
<br><t style="margin-left: 30px">self.i := i; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToString: string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'r1("{string(s)}", {i})'; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">var a1 := new r1('abcd', 1234); </t>
|
||||
<br><t style="margin-left: 10px">var b1 := new r1('abcd' * 4, 5678); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">{ </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write(a1, b1); </t>
|
||||
<br><t style="margin-left: 10px">{} </t>
|
||||
<br><t style="margin-left: 10px">f.Pos := 0; </t>
|
||||
<br><t style="margin-left: 10px">var a2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">var b2 := f.Read; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b1); </t>
|
||||
<br><t style="margin-left: 10px">writeln(string(a1.s).Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(string(b1.s).Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">writeln(a2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(b2); </t>
|
||||
<br><t style="margin-left: 10px">writeln(string(a2.s).Length); </t>
|
||||
<br><t style="margin-left: 10px">writeln(string(b2.s).Length); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Конечно, памяти будет нужен на много больший объём для такого сохранения.<br>
|
||||
А т.к. памяти будет больше - значит и записывать/читать придётся больше, то есть производительность тоже ниже.<br>
|
||||
Хотя в целом - такой способ должен быть проще в реализации.
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
<font size="5">Сохранение ссылочных типов в блочный файл.</font><br>
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">В чём проблема ссылочных типов</font></p>
|
||||
<div id=decl style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">r1 = record </t>
|
||||
<br><t style="margin-left: 20px">b: byte; </t>
|
||||
<br><t style="margin-left: 20px">i: integer; </t>
|
||||
<br><t style="margin-left: 20px">s: string; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var a: r1; </t>
|
||||
<br><t style="margin-left: 10px">a.b := 5; </t>
|
||||
<br><t style="margin-left: 10px">a.i := 12345; </t>
|
||||
<br><t style="margin-left: 10px">a.s := 'abcdef'*10000;//Это 60 000 символов, ~120KB. А значит в записи никаким образом не поместилось бы </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
r1, byte и integer из этого примера - размерные типы (записи), а значит они хранят свои данные прямо в переменных.<br>
|
||||
А string - ссылочный тип (класс). Это значит что в поле r1.s хранится только ссылка (особый указатель) на содержимое строки.<br>
|
||||
<br>
|
||||
То есть, в переменной "a" хранится три значения: byte, integer и ссылка.<br>
|
||||
А содержимое строки хранится где-то в оперативной памяти, куда указывает эта ссылка.<br>
|
||||
<br>
|
||||
"BlockFileOf<T>" берёт содержимое переменных одним блоком памяти.<br>
|
||||
Поэтому, если попытаться сохранить содержимое r1 в типизированный файл.<br>
|
||||
(отключив защиту от дурака, запрещающую ссылочные типы)<br>
|
||||
В файл сохранится значение ссылки, а не строки.<br>
|
||||
<br>
|
||||
Кроме того, ссылки отличаются от указателей тем, что они ссылаются на значение, которое может "плавать" в оперативной памяти.<br>
|
||||
Точнее, сборщик мусора может двигать эти значения, для экономии места.<br>
|
||||
Это значит что если сохранить запись с ссылкой в файл и сразу загрузить эту ссылку назад.<br>
|
||||
Её значение может быть уже устаревшим.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Размерные массивы и строки</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
|
||||
Посмотрите в этот файл:<br>
|
||||
C:\PABCWork.NET\Samples\BlockFileOfT\Сравнение скорости.pas<br>
|
||||
Найдите там тип "ValueString255".<br>
|
||||
На нём будут строиться дальнейшие обноснования:<br>
|
||||
Этот тип представляет строку с фиксированной длинной, а точнее длиной в 255 символов.<br>
|
||||
<br>
|
||||
Первое, что видно - прямо перед объявлением типа стоит атрибут "StructLayout":<br>
|
||||
"LayoutKind.Explicit" разрешает применять атрибут "FieldOffset" к полям.<br>
|
||||
А "Size" позволяет явно указать сколько байт выделять под данную запись.<br>
|
||||
Благодаря последнему - можно выделить место сразу под все 255 символов строки, не делая 255 полей с типом "char".<br>
|
||||
<br>
|
||||
Далее, в записи "ValueString255" есть всего 2 поля, "length" (длина) и "body" (тело, а точнее его начало)<br>
|
||||
"length" это кол-во символов во всей строке. В данном случае не может быть больше 255 символов,<br>
|
||||
поэтому достаточно 1 байта чтобы хранить любую возможную длину строки.<br>
|
||||
А "body" это начало самогО содержимого строки.<br>
|
||||
Вообще, поле "body" не так уж и нужно, но с ним будет проще, потому что если создать указатель на "body" -<br>
|
||||
он сразу оказывается и указателем на содержимое строки, без каких-либо преобразований.<br>
|
||||
<br>
|
||||
Далее идут методы для преобразования между обычной строкой и нашей размерной.<br>
|
||||
Я сделал их "operator explicit"-ами, потому что их вызов красивый и удобный.<br>
|
||||
<br>
|
||||
Смотрим в первый, преобразовывающий массив символов в размерную строку:<br>
|
||||
<br>
|
||||
Если выполнить "Result.length := a.Length" и передать массив длинной больше 255 символов -<br>
|
||||
в этой строчке произойдёт переполнение и "Result.length" запонится мусором, поэтому сначала надо уменьшить значение до "MaxLength".<br>
|
||||
<br>
|
||||
Далее, если длина равна нулю - ничего больше делать не надо.<br>
|
||||
В противном случае - используем процедуру "CopyMem".<br>
|
||||
<br>
|
||||
Насчёт того, как работает "CopyMem", можно написать целую диссертацию.<br>
|
||||
Но главное, что надо знать вам: если попытаться получить указатель на элемент массива ("@a[n]") -<br>
|
||||
получаем утечку памяти. Однако, если передать "a[n]" в "CopyMem" как var-параметр<br>
|
||||
(что, по сути, тоже создаёт указатель на этот элемент массива),<br>
|
||||
то всё в порядке.<br>
|
||||
<br>
|
||||
В данном случае мы копируем память начиная с нулевого элемента массива<br>
|
||||
Вставляем её в "Result.body"<br>
|
||||
И всего копируем "Result.length*2" байт (*2 потому что каждый символ занимает 2 байта в памяти)<br>
|
||||
<br>
|
||||
Преобразование из "string" в размерную строку работает также, там ничего интересного.<br>
|
||||
У преобразования размерной строки в массив символов всего одна особенность:<br>
|
||||
Теперь результат у нас ссылочный (массив), поэтому память под него нужно выделить вручную ("new char[...]").<br>
|
||||
<br>
|
||||
И последнее преобразование, из "ValueString255" в "string":<br>
|
||||
А вот тут очень приятно вышло - у "string" есть конструктор который идеально подходит:<br>
|
||||
Первый параметр это указатель на начало блока памяти содержащего символы. То есть "@s.body" и даже тип подходит без преобразований.<br>
|
||||
Второй параметр - смещение начала. У нас символы начинаются прямо на "body", поэтому 0.<br>
|
||||
И последний - количество символов.<br>
|
||||
<br>
|
||||
Последнее, что есть в данной записи - перегрузка "ToString". Это вообще не обязательный код,<br>
|
||||
но с этой функцией размерная строка будет вести себя точно так же, как и обычная, если передать её во "Writeln".<br>
|
||||
Также можно добавлять и другие свои методы в данный тип и даже сделать все те же методы, как и у ссылочных строк, или даже больше.<br>
|
||||
<br>
|
||||
Ну а размерные массивы ничем не отличаются в реализации.<br>
|
||||
Можно даже сказать, что "ValueString255" это размерный массив символов, ничего от этого не изменится.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,108 +1,110 @@
|
|||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
<hr />
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Что такое "Offset"</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
"Offset", он же отступ - это кол-во байт в начале файла, которые запрещенно использовать под элементы, сохранённые в файл.<br>
|
||||
При этом, в эту область можно использовать под что угодно.<br>
|
||||
Чаще всего - заголовок файла, описывающий какую то, общую для всех элементов файла, информацию.<br>
|
||||
<br>
|
||||
Если у файла есть отступ - "Size" и "Pos" будут учитывать его.<br>
|
||||
То есть, если длина файла 11 байт, 1 элемент занимает 2 байт, а отступ 3 байт - "Size" будет возвращать 4, потому что (11-3)/2 .<br>
|
||||
<br>
|
||||
Но, что очень важно помнить - когда файл только открыт/создан любым способом, кроме "Append" -<br>
|
||||
позиция в файле находится в самом начале, то есть на отсутпе.<br>
|
||||
А попытка прочитать/записать элемент на отустп, конечно, ничем хорошим не закончится.<br>
|
||||
Поэтому, в большинстве случаев лучше вызвать "f.Pos := 0" перед чтением/записью.<br>
|
||||
Это поставить позицию в файле на элемент #0, то есть сразу после отступа.
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">Заголовки</font></p>
|
||||
<div id=decl style="display: none">
|
||||
И так, как написано выше, основное применение - заголовок файла:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">HeaderT = record </t>
|
||||
<br><t style="margin-left: 20px">version: integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToString:string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'Header:[version={version}]'; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">ElementT = byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<ElementT>('temp.bin',sizeof(HeaderT));//Этот конструктор установит Offset на sizeof(HeaderT) </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">begin </t>
|
||||
<br><t style="margin-left: 20px">var hf := new BlockFileOf<HeaderT>(f);//Этот конструктор создаст новый "BlockFileOf<T>", у которого будет общий фаловый поток с "f" </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">var h: HeaderT; </t>
|
||||
<br><t style="margin-left: 20px">h.version := 5; </t>
|
||||
<br><t style="margin-left: 20px">hf.Write(h); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">hf.Close;//Эта строчка не закроет поток, потому что файл сейчас открыт в 2 переменных </t>
|
||||
<br><t style="margin-left: 65px">//Но она отвяжет "hf" от файла открытого в "f", чтоб когда "f" закроется - файл тоже был закрыт </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5,6,7,8,9 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close;//А вот теперь поток будет закрыт и файл сохранён, потому что теперь это последняя перменная, в которой открыт файл </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">begin </t>
|
||||
<br><t style="margin-left: 20px">var hf := new BlockFileOf<HeaderT>(f); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">var h := hf.Read; </t>
|
||||
<br><t style="margin-left: 20px">writeln(h); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">hf.Close;//Опять же, надо закрыть сразу, как закончили пользоваться </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">f.Read(9).Print </t>
|
||||
<br><t style="margin-left: 10px">f.Close </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Прошу обратить внимание на то, что я не поставил тут "f.Pos := 0" перед чтением/записью только по стечению 2 обстоятельств:<br>
|
||||
1. Этот код линейный, в нём хорошо видно что прямо перед чтением/записью элементов идёт чтение/запись заголовка. А значит, файловая позиция сама передвинется на sizeof(HeaderT).<br>
|
||||
2. Написав этот короткий участок кода - я протестировал его, чтоб быть увереным что ничего не забыл и не перепутал.
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
<hr />
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Что такое "Offset"</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
"Offset" (он же отступ) — кол-во байт в начале файла, которые не будут использоваться под элементы, сохранённые в файл.<br>
|
||||
При этом, эту область можно использовать под что угодно другое.<br>
|
||||
Чаще всего — под заголовок файла, описывающий какую-то общую для всех элементов файла информацию.<br>
|
||||
<br>
|
||||
Если у файла есть отступ — "Size" и "Pos" будут учитывать его.<br>
|
||||
То есть, если длина файла 11 байт, один элемент занимает 2 байт, а отступ 3 байт - "Size" будет возвращать 4, потому что (11-3)/2 = 4.<br>
|
||||
<br>
|
||||
Но, что очень важно помнить: когда файл только открыт/создан любым способом, кроме "Append" -<br>
|
||||
позиция в файле находится в самом начале, то есть на отступе.<br>
|
||||
А значит, операции чтения и записи будут начинать работать с самого начала файла, а не с конца отступа.<br>
|
||||
Поэтому, в большинстве случаев лучше вызвать "f.Pos := 0" перед чтением/записью.<br>
|
||||
Это поставит позицию в файле на элемент #0, то есть сразу после отступа.
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">Заголовки</font></p>
|
||||
<div id=decl style="display: none">
|
||||
И так, как написано выше, основное применение - заголовок файла:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">HeaderT = record </t>
|
||||
<br><t style="margin-left: 20px">version: integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">public function ToString: string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'Header: [version={version}]'; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">ElementT = byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<ElementT>('temp.bin',sizeof(HeaderT)); // Этот конструктор установит Offset на sizeof(HeaderT) </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">begin </t>
|
||||
<br><t style="margin-left: 20px">var hf := new BlockFileOf<HeaderT>(f); // Этот конструктор создаст новый "BlockFileOf<T>", у которого будет общий фаловый поток с "f" </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">var h: HeaderT; </t>
|
||||
<br><t style="margin-left: 20px">h.version := 5; </t>
|
||||
<br><t style="margin-left: 20px">hf.Write(h); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">// Эта строчка не закроет поток, потому что файл сейчас открыт в двух переменных </t>
|
||||
<br><t style="margin-left: 20px">// Но она отвяжет "hf" от файла открытого в "f", чтобы когда "f" закрылся - файл тоже был закрыт </t>
|
||||
<br><t style="margin-left: 20px">hf.Close; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5,6,7,8,9 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; // А вот теперь поток будет закрыт и файл сохранён, потому что теперь это последняя перменная, в которой открыт файл </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">begin </t>
|
||||
<br><t style="margin-left: 20px">var hf := new BlockFileOf<HeaderT>(f); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">var h := hf.Read; </t>
|
||||
<br><t style="margin-left: 20px">writeln(h); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">hf.Close;//Опять же, надо закрыть сразу, как закончили пользоваться </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 10px">f.Read(9).Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
Прошу обратить внимание на то, что я не поставил тут "f.Pos := 0" перед чтением/записью только по стечению двух обстоятельств:<br>
|
||||
1. Этот код линейный, в нём хорошо видно, что прямо перед чтением/записью элементов идёт чтение/запись заголовка. А значит, файловый курсор сам передвинется на sizeof(HeaderT).<br>
|
||||
2. Написав этот короткий участок кода я протестировал его, чтобы быть уверенным в том, что я ничего не забыл и не перепутал.
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,264 +1,265 @@
|
|||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
Перед тем как начать, хоть уточнить:<br>
|
||||
Ожидается что у вас есть хоть какое то представление о "file of T".<br>
|
||||
Конечно, без того можно разобраться, но будет сложнее.
|
||||
<hr />
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">Объявление</font></p>
|
||||
<div id=decl style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of T; </t>
|
||||
<br><t style="margin-left: 10px">Assign(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: BlockFileOf<T>; </t>
|
||||
<br><t style="margin-left: 10px">f := new BlockFileOf<T>;//В отличии от file of T - BlockFileOf<T> надо всегда инициализировать </t>
|
||||
<br><t style="margin-left: 10px">f.Assign('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">Последнее можно сократить:</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<T>; </t>
|
||||
<br><t style="margin-left: 10px">f.Assign('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">И ещё больше сократить:</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<byte>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Основы записи/чтения</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Rewrite(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Reset(f); </t>
|
||||
<br><t style="margin-left: 10px">loop 5 do </t>
|
||||
<br><t style="margin-left: 20px">f.Read.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.Read(5).Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
В случае с "BlockFileOf<T>" все 5 элементов читаются как 1 блок памяти.<br>
|
||||
В то время как "file of T" может прочитать только по 1 элементу.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('to_seq')"><font size="4" color="008000" style="cursor: pointer">Перечисление всех элементов в файле</font></p>
|
||||
<div id=to_seq style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Rewrite(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Reset(f); </t>
|
||||
<br><t style="margin-left: 10px">f.ReadElements.Print </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.ToSeq.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
"BlockFileOf<T>.ToSeq" работает так же как и "(file of T).ReadElements" (если не считать более продвинутой защиты от дурака):<br>
|
||||
Они оба читают файл по 1 элементу, и после каждого из них проверяют, не достигнут ли конец файла.<br>
|
||||
<br>
|
||||
Но в "BlockFileOf<T>" есть альтернатива - "ToSeqBlocks".<br>
|
||||
Эта функция читает всё из файла блоками заданной (в байтах) длины. Если её не задать - будет по умолчанию, то есть 4КБ.<br>
|
||||
В нашем случае, "integer" занимает 4 байта в памяти, значит в каждом блоке по 4КБ поместится 1024 элемента типа "integer"<br>
|
||||
(Объём типа T можно узнать из свойства "BlockFileOf<T>.TSize", или с помощью sizeof(T) )<br>
|
||||
В случае с 5 элементами это едва ли даст преимущество, но если записывать и считывать сразу много элементов - считывание блоками будет на много быстрее:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">var blocks := f.ToSeqBlocks; </t>
|
||||
<br><t style="margin-left: 10px">var AsSeq := blocks.SelectMany(bl->bl); </t>
|
||||
<br><t style="margin-left: 10px">AsSeq.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
"AsSeq := blocks.SelectMany(bl->bl)" здесь делает так, что хоть всё считывается из файла блоками,<br>
|
||||
переменную "AsSeq" будет воспринимать как последовательность элементов, а не последовательность блоков.<br>
|
||||
И - я расписал вывод как 3 строчки чтоб было виднее. Можно и сократить:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.ToSeqBlocks.SelectMany(bl->bl).Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('bfo_record')"><font size="4" color="008000" style="cursor: pointer">Основное преимущество: сохранение записей (record)</font></p>
|
||||
<div id=bfo_record style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">r1=record </t>
|
||||
<br><t style="margin-left: 20px">b1:byte; </t>
|
||||
<br><t style="margin-left: 20px">i:integer; </t>
|
||||
<br><t style="margin-left: 20px">b2:byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">constructor(b1:byte; i:integer; b2:byte); </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">self.b1 := b1; </t>
|
||||
<br><t style="margin-left: 30px">self.i := i; </t>
|
||||
<br><t style="margin-left: 30px">self.b2 := b2; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 20px">///Эта функция определяет, как данную запись выведет на экран </t>
|
||||
<br><t style="margin-left: 20px">function ToString:string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'r1(b1={b1}, i={i}, b2={b2})'; </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">new r1(1,2,3) </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">writeln(f.Read); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
|
||||
</p>
|
||||
Казалось бы, тот же самый код будет работать и для "file of T".<br>
|
||||
Но вся разница находится в реализации:<br>
|
||||
<br>
|
||||
"file of T" в данном случае, при вызове "Read" сначала прочитал бы названия и типы полей "r1" из его типа (через "System.Runtime"),<br>
|
||||
а затем, по очереди, читал бы из файла значение того же типа, что и у поля ("byte", затем "integer" и снова "byte"),<br>
|
||||
сохраняя прочитаные значения в соответствующих полях результата.<br>
|
||||
<br>
|
||||
А "BlockFileOf<T>" подходит к этому с другой стороны.<br>
|
||||
Когда вы первый раз инициализируете переменную типа "BlockFileOf<r1>" -<br>
|
||||
объём в памяти занимаемый переменными типа "r1" вычисляется и сохраняется во внутрее статичное поле<br>
|
||||
(Его можно получить через свойство "TSize")<br>
|
||||
И когда вызывается функция "Read" - "BlockFileOf<T>" читает из файла столько байт, сколько занимают переменные типа "r1",<br>
|
||||
А потом копирует содержимое полученного массива байт в результат.<br>
|
||||
<br>
|
||||
То есть, "file of T" читает 3 блока, а "BlockFileOf<T>" только 1.<br>
|
||||
И когда объём данных одинаковый - количество блоков очень важный фактор.<br>
|
||||
Поэтому "BlockFileOf<T>" будет быстрее.<br>
|
||||
И размер 1 блока не ограничивается размером 1 переменной типа "r1".<br>
|
||||
"Read(10)", к примеру, прочитает 1 блоком то, что "file of T" читал бы как 30 блоков.<br>
|
||||
<br>
|
||||
Ну и, конечно, всё то же самое касается и "Write", не только "Read".<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css">
|
||||
.code_block {
|
||||
background: #c0c0c0;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
border: solid 1px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggle_show(id) {
|
||||
document.getElementById(id).style.display = document.getElementById(id).style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<font size="3"><a href="../Справка.html"><=</a></font><br>
|
||||
<br>
|
||||
Основной упор данной страницы - сравнение "file of T" и "BlockFileOf<T>".<br>
|
||||
Но она также подходит и для обучения основам "BlockFileOf<T>".
|
||||
<hr />
|
||||
|
||||
|
||||
<p onClick="toggle_show('decl')"><font size="4" color="008000" style="cursor: pointer">Объявление</font></p>
|
||||
<div id=decl style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of T; </t>
|
||||
<br><t style="margin-left: 10px">Assign(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: BlockFileOf<T>; </t>
|
||||
<br><t style="margin-left: 10px">f := new BlockFileOf<T>; // В отличии от file of T - BlockFileOf<T> нужно всегда инициализировать </t>
|
||||
<br><t style="margin-left: 10px">f.Assign('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">Последнее можно сократить:</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<T>; </t>
|
||||
<br><t style="margin-left: 10px">f.Assign('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">Или ещё больше сократить:</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<byte>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('io_basics')"><font size="4" color="008000" style="cursor: pointer">Основы записи/чтения</font></p>
|
||||
<div id=io_basics style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Rewrite(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Reset(f); </t>
|
||||
<br><t style="margin-left: 10px">loop 5 do </t>
|
||||
<br><t style="margin-left: 20px">f.Read.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.Read(5).Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
В случае с "BlockFileOf<T>" все 5 элементов читаются одним блоком памяти.<br>
|
||||
В то время как "file of T" читает по одному элементу.<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('to_seq')"><font size="4" color="008000" style="cursor: pointer">Перечисление всех элементов в файле</font></p>
|
||||
<div id=to_seq style="display: none">
|
||||
<font size="3">"file of T":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f: file of integer; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Rewrite(f, 'temp.bin'); </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">Reset(f); </t>
|
||||
<br><t style="margin-left: 10px">f.ReadElements.Print </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
<font size="3">"BlockFileOf<T>":</font>
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.ToSeq.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
"BlockFileOf<T>.ToSeq" работает так же, как и "(file of T).ReadElements" (если не считать более продвинутой защиты от дурака):<br>
|
||||
Они оба читают файл по одному элементу и после каждого из них проверяют, не достигнут ли конец файла.<br>
|
||||
<br>
|
||||
Но в "BlockFileOf<T>" есть альтернатива - "ToSeqBlocks".<br>
|
||||
Эта функция читает всё из файла блоками заданной длины в байтах. Если не задавать длину, данные будут читаться блоками по 4 килобайта.<br>
|
||||
В нашем случае, "integer" занимает 4 байта в памяти, значит в каждом блоке по 4 КБ поместится 1024 элемента типа "integer"<br>
|
||||
(Объём типа T можно узнать из свойства "BlockFileOf<T>.TSize", или с помощью sizeof(T) )<br>
|
||||
В случае с пятью элементами это едва ли даст преимущество, но если записывать и считывать сразу большое количество элементов - считывание блоками будет намного быстрее:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<integer>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">1,2,3,4,5 </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">var blocks := f.ToSeqBlocks; </t>
|
||||
<br><t style="margin-left: 10px">var AsSeq := blocks.SelectMany(bl->bl); </t>
|
||||
<br><t style="margin-left: 10px">AsSeq.Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
</p>
|
||||
"AsSeq := blocks.SelectMany(bl->bl)" здесь делает так, что хоть всё считывается из файла блоками,<br>
|
||||
переменную "AsSeq" будет воспринимать как последовательность элементов, а не последовательность блоков.<br>
|
||||
И - я расписал вывод как 3 строчки чтоб было виднее. Можно и сократить:
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">f.ToSeqBlocks.SelectMany(bl->bl).Print; </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p onClick="toggle_show('bfo_record')"><font size="4" color="008000" style="cursor: pointer">Основное преимущество: сохранение записей (record)</font></p>
|
||||
<div id=bfo_record style="display: none">
|
||||
<p class=code_block>
|
||||
<t style="margin-left: 00px">uses BlockFileOfT; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">type </t>
|
||||
<br><t style="margin-left: 10px">r1 = record </t>
|
||||
<br><t style="margin-left: 20px">b1: byte; </t>
|
||||
<br><t style="margin-left: 20px">i: integer; </t>
|
||||
<br><t style="margin-left: 20px">b2: byte; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">constructor(b1: byte; i: integer; b2: byte); </t>
|
||||
<br><t style="margin-left: 20px">begin </t>
|
||||
<br><t style="margin-left: 30px">self.b1 := b1; </t>
|
||||
<br><t style="margin-left: 30px">self.i := i; </t>
|
||||
<br><t style="margin-left: 30px">self.b2 := b2; </t>
|
||||
<br><t style="margin-left: 20px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 20px">///Эта функция определяет то, как данную запись выведет на экран </t>
|
||||
<br><t style="margin-left: 20px">function ToString: string; override := </t>
|
||||
<br><t style="margin-left: 20px">$'r1(b1={b1}, i={i}, b2={b2})'; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">end; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">begin </t>
|
||||
<br><t style="margin-left: 10px">var f := new BlockFileOf<r1>('temp.bin'); </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Rewrite; </t>
|
||||
<br><t style="margin-left: 10px">f.Write( </t>
|
||||
<br><t style="margin-left: 20px">new r1(1,2,3) </t>
|
||||
<br><t style="margin-left: 10px">); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 10px">f.Reset; </t>
|
||||
<br><t style="margin-left: 10px">writeln(f.Read); </t>
|
||||
<br><t style="margin-left: 10px">f.Close; </t>
|
||||
<br><t style="margin-left: 00px"> </t>
|
||||
<br><t style="margin-left: 00px">end. </t>
|
||||
|
||||
</p>
|
||||
Казалось бы, тот же самый код будет работать и для "file of T".<br>
|
||||
Но вся разница находится в реализации:<br>
|
||||
<br>
|
||||
"file of T" в данном случае при вызове "Read" сначала прочитал бы названия и типы полей "r1" (через "System.Reflection"),<br>
|
||||
а затем, по очереди, читал бы из файла значение того же типа, что и у поля ("byte", затем "integer" и снова "byte"),<br>
|
||||
сохраняя прочитаные значения в соответствующих полях результата.<br>
|
||||
<br>
|
||||
А "BlockFileOf<T>" подходит к этому с другой стороны.<br>
|
||||
Когда вы первый раз инициализируете переменную типа "BlockFileOf<r1>" -<br>
|
||||
объём в памяти, занимаемый переменными типа "r1", вычисляется и сохраняется во внутреннее статическое поле<br>
|
||||
(Его можно получить через свойство "TSize")<br>
|
||||
И когда вызывается функция "Read" - "BlockFileOf<T>" читает из файла столько байт, сколько занимают переменные типа "r1",<br>
|
||||
А потом копирует содержимое полученного массива байт в результат.<br>
|
||||
<br>
|
||||
То есть, "file of T" читает три блока, а "BlockFileOf<T>" только один.<br>
|
||||
И когда объём данных одинаковый - количество блоков очень важный фактор.<br>
|
||||
Поэтому "BlockFileOf<T>" будет быстрее.<br>
|
||||
И размер одного блока не ограничивается размером одной переменной типа "r1".<br>
|
||||
"Read(10)", к примеру, прочитает одним блоком то, что "file of T" читал бы как 30 блоков.<br>
|
||||
<br>
|
||||
Ну и, конечно, всё то же самое касается и "Write".<br>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,116 +1,97 @@
|
|||
uses BlockFileOfT;
|
||||
|
||||
// Эта процедура позволяет копировать память между содержимым массива и другим массивом/стеком
|
||||
// При этом не волнуясь о блокировках массивов в памяти
|
||||
// Если элемент массива передан как var-параметр, то сборщик мусора не трогает этот массив
|
||||
procedure CopyMem<T1,T2>(var o1: T1; var o2: T2; count: integer) :=
|
||||
System.Buffer.MemoryCopy(
|
||||
@o1, @o2,
|
||||
count, count
|
||||
);
|
||||
|
||||
type
|
||||
StructLayout = System.Runtime.InteropServices.StructLayoutAttribute;
|
||||
LayoutKind = System.Runtime.InteropServices.LayoutKind;
|
||||
FieldOffset = System.Runtime.InteropServices.FieldOffsetAttribute;
|
||||
|
||||
GCHandle = System.Runtime.InteropServices.GCHandle;
|
||||
GCHandleType = System.Runtime.InteropServices.GCHandleType;
|
||||
|
||||
|
||||
|
||||
//Эти 2 типа взят из справки, и по рекомендациям от туда переделан под наш случай
|
||||
//имеется в виду справка, которая html файл, лежащий в папке с этим примером
|
||||
[StructLayout(LayoutKind.&Explicit)]
|
||||
CharArr255Body = record
|
||||
private const MaxLength = 255;
|
||||
private const TSize = 2;
|
||||
private const Size = MaxLength * TSize;
|
||||
[FieldOffset(Size-1)] last: byte;
|
||||
end;
|
||||
[StructLayout(LayoutKind.&Explicit)]
|
||||
CharArr255 = record
|
||||
// Size контролирует размер записи. Его можно не указывать, тогда его выберет автоматически
|
||||
// В нашем случае нужен один байт для поля length
|
||||
// А затем место под 255 символов (масимум, который может хранить эта строка)
|
||||
// При этом каждый символ занимает два байта в памяти
|
||||
[StructLayout(LayoutKind.&Explicit, Size= 1 + 255*2 )]
|
||||
ValueString255 = record
|
||||
public [FieldOffset(0)] length: byte;
|
||||
public [FieldOffset(1)] body: CharArr255Body;
|
||||
public [FieldOffset(1)] body: char;
|
||||
|
||||
public class function operator explicit(a: array of char): CharArr255;
|
||||
public const MaxLength = 255;
|
||||
|
||||
// operator explicit, принимающий array of char и возвращающий ValueString255 выглядит в коде как:
|
||||
// var s := ValueString255(a);
|
||||
// Где тип у "a" — array of char
|
||||
public static function operator explicit(a: array of char): ValueString255;
|
||||
begin
|
||||
Result.length := Min(CharArr255Body.MaxLength, a.Length);//если a.Length>MaxLength - length присвоит MaxLength
|
||||
|
||||
if a.Length < CharArr255Body.MaxLength then
|
||||
a := a + new char[CharArr255Body.MaxLength - a.Length];//если a.Length<MaxLength - надо удлинить
|
||||
//иначе можем иногда получать ошибки чтения защищённой памяти
|
||||
//если откажет в доступе к памяти, которая находиться полсе массива
|
||||
|
||||
//надо заблокировать массив на 1 месте в памяти, чтоб сборщик мусора не двигал пока мы копируем
|
||||
var hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
var ptr: ^CharArr255Body := pointer(hnd.AddrOfPinnedObject);
|
||||
Result.body := ptr^;
|
||||
finally
|
||||
hnd.Free;//в finally, чтоб если возникнет какая то ошибка - блокировку всё равно сняло
|
||||
//иначе получим утечку памяти, потому что блокировка запрещает сборщику мусора освобождать память
|
||||
end;
|
||||
Result.length := Min(MaxLength, a.Length); // если a.Length>MaxLength, то length надо обрезать до MaxLength
|
||||
if Result.length=0 then exit; // иначе упадёт a[0]
|
||||
CopyMem(a[0], Result.body, Result.length*2); // char занимает два байта, поэтому копируем length*2
|
||||
end;
|
||||
|
||||
public class function operator explicit(s: string): CharArr255 :=
|
||||
CharArr255(s.ToCharArray);
|
||||
|
||||
public class function operator explicit(a: CharArr255): array of char;
|
||||
public static function operator explicit(s: string): ValueString255;
|
||||
begin
|
||||
//Если кол-во сохранённых байт < MaxLength - a.body будет занимать бОльший объём в памяти, чем поместится в Result
|
||||
//Поэтому надо сначала прочитать всё, включая пустые символы на конце, во временную переменную
|
||||
//А затем скопировать нужное кол-во символов в Result
|
||||
var temp := new char[CharArr255Body.MaxLength];
|
||||
|
||||
var hnd := GCHandle.Alloc(temp, GCHandleType.Pinned);
|
||||
try
|
||||
var ptr: ^CharArr255Body := pointer(hnd.AddrOfPinnedObject);
|
||||
ptr^ := a.body;
|
||||
finally
|
||||
hnd.Free;
|
||||
end;
|
||||
|
||||
if a.length = CharArr255Body.MaxLength then
|
||||
begin
|
||||
Result := temp;//всё же, копирование ссылки - это быстрее, чем копировать всё содержимое массива
|
||||
exit;
|
||||
end;
|
||||
|
||||
Result := new char[a.length];
|
||||
System.Array.Copy(temp, Result, a.length);
|
||||
Result.length := Min(MaxLength, s.Length);
|
||||
if Result.length=0 then exit;
|
||||
CopyMem(s[1], Result.body, Result.length*2);
|
||||
end;
|
||||
|
||||
public function ToRefArr: array of char;
|
||||
type
|
||||
CharArr = array of char;
|
||||
public static function operator explicit(s: ValueString255): array of char;
|
||||
begin
|
||||
Result := new char[s.length];
|
||||
if s.length=0 then exit;
|
||||
CopyMem(s.body, Result[0], s.length*2);
|
||||
end;
|
||||
|
||||
// объявлять тип CharArr может быть неудобно
|
||||
public function ToCharArray: array of char;
|
||||
type CharArr = array of char;
|
||||
begin
|
||||
Result := CharArr(self);
|
||||
end;
|
||||
|
||||
public function ToString: string; override :=
|
||||
new string(ToRefArr);
|
||||
public static function operator explicit(s: ValueString255): string :=
|
||||
new string(@s.body, 0, s.length);
|
||||
|
||||
public class function operator explicit(s: CharArr255): string :=
|
||||
s.ToString;
|
||||
public function ToString: string; override :=
|
||||
string(self);
|
||||
|
||||
end;
|
||||
|
||||
///Это будет сохранять в file of T
|
||||
AR = record
|
||||
s: string[255];
|
||||
//dt: System.DateTime;//Не даёт ¯\_(ツ)_/¯
|
||||
dt: int64;//Ну ландо, в System.DateTime всё хранится в 1 поле типа int64
|
||||
|
||||
//dt: DateTime; // Не даёт ¯\_(ツ)_/¯
|
||||
dt: int64; // Ну и ладно, в System.DateTime всё хранится в одном поле типа int64
|
||||
|
||||
i: integer;
|
||||
ch: char;
|
||||
b: byte;
|
||||
end;
|
||||
///Это будет сохранять в BlockFileOfT
|
||||
///Это будет сохранять в BlockFileOf<T>
|
||||
BR = record
|
||||
s: CharArr255;
|
||||
//dt: System.DateTime;//И тут тоже - DateTime это "особый тип", подробнее в справке
|
||||
dt: int64;//Ну а мы - знаем 1 версию, ту что сейчас, и в ней всё хранится как внутренне поле типа int64
|
||||
s: ValueString255;
|
||||
dt: DateTime; // А BlockFileOf<T> принимает любые размерные типы без ограничений
|
||||
i: integer;
|
||||
//ch: char;//И тут, опять, особый тип. Ну, word хранит информацию так же как char
|
||||
ch: word;//В типе IOR описаны все преобразования, как превратить эти типы в System.DateTime и char
|
||||
ch: char;
|
||||
b: byte;
|
||||
end;
|
||||
|
||||
///Этот тип для ввода/вывода
|
||||
///Чтоб и string[255] и CharArr255 преобразовывало в string
|
||||
///Так честнее
|
||||
///Хотя при преобразовании string[255] к string почти ничего не происходит, только копируется одна ссылка
|
||||
///Это тип для ввода/вывода
|
||||
///В нём хранятся входные данные, общие и для file of T, и для BlockFileOf<T>
|
||||
///Тут же описаны и преобразования всех особых типов
|
||||
///Заметьте, преобразование string[255] к string и назад ничего не копирует и не преобразовывает, если изначальная строка уже была <= 255 символов
|
||||
///Поэтому преобразования между ValueString255 и string работают медленнее
|
||||
///И даже при этом BlockFileOf<T> всё равно быстрее
|
||||
IOR = record
|
||||
s: string;
|
||||
dt: System.DateTime;
|
||||
|
|
@ -118,43 +99,43 @@ type
|
|||
ch: char;
|
||||
b: byte;
|
||||
|
||||
class function operator explicit(a: IOR): AR;
|
||||
static function operator explicit(a: IOR): AR;
|
||||
begin
|
||||
Result.s := a.s;
|
||||
Result.dt := a.dt.Ticks;
|
||||
Result.dt := a.dt.Ticks; // Ticks возвращает никак не преобразованное значение единственного внутреннего поля DateTime
|
||||
Result.i := a.i;
|
||||
Result.ch := a.ch;
|
||||
Result.b := a.b;
|
||||
end;
|
||||
|
||||
class function operator explicit(a: IOR): BR;
|
||||
static function operator explicit(a: IOR): BR;
|
||||
begin
|
||||
Result.s := CharArr255(a.s);
|
||||
Result.dt := a.dt.Ticks;
|
||||
Result.i := a.i;
|
||||
Result.ch := word(a.ch);
|
||||
Result.b := a.b;
|
||||
end;
|
||||
|
||||
class function operator explicit(a: AR): IOR;
|
||||
begin
|
||||
Result.s := a.s;
|
||||
Result.dt := new System.DateTime(a.dt);
|
||||
Result.s := ValueString255(a.s); // вызываем наш operator explicit
|
||||
Result.dt := a.dt;
|
||||
Result.i := a.i;
|
||||
Result.ch := a.ch;
|
||||
Result.b := a.b;
|
||||
end;
|
||||
|
||||
class function operator explicit(a: BR): IOR;
|
||||
static function operator explicit(a: AR): IOR;
|
||||
begin
|
||||
Result.s := new string(a.s.ToRefArr);
|
||||
Result.dt := new System.DateTime(a.dt);
|
||||
Result.s := a.s;
|
||||
Result.dt := new DateTime(a.dt); // Единственный конструктор DateTime принимающий 1 параметр - принимает кол-во тиков. И напрямую присваивает это значение внутреннему полю
|
||||
Result.i := a.i;
|
||||
Result.ch := char(a.ch);
|
||||
Result.ch := a.ch;
|
||||
Result.b := a.b;
|
||||
end;
|
||||
|
||||
class function GetRandom: IOR;
|
||||
static function operator explicit(a: BR): IOR;
|
||||
begin
|
||||
Result.s := string(a.s);
|
||||
Result.dt := a.dt;
|
||||
Result.i := a.i;
|
||||
Result.ch := a.ch;
|
||||
Result.b := a.b;
|
||||
end;
|
||||
|
||||
static function GetRandom: IOR;
|
||||
begin
|
||||
Result.s := new string(ArrGen(Random(256), i -> ChrAnsi(Random(byte.MaxValue))));
|
||||
Result.dt := System.DateTime.Now.AddTicks(System.Convert.ToInt64(((Random * 2 - 1) * 1024 * 1024 * 1024 * 1024)));
|
||||
|
|
@ -233,9 +214,9 @@ end;
|
|||
|
||||
procedure TestIntegrity;
|
||||
begin
|
||||
TestIntegrity1(1);
|
||||
TestIntegrity1(10000);
|
||||
writeln('тест 1 ok');
|
||||
TestIntegrity2(1024);
|
||||
TestIntegrity2(10000);
|
||||
writeln('тест 2 ok');
|
||||
writeln('тесты ok');
|
||||
end;
|
||||
|
|
@ -243,10 +224,10 @@ end;
|
|||
procedure TestSpeed;
|
||||
begin
|
||||
|
||||
var sw := new System.Diagnostics.Stopwatch;//точнее чем этим замерить невозможно
|
||||
var sw := new System.Diagnostics.Stopwatch; // Точнее, чем этим, замерить невозможно
|
||||
var lc := 10;
|
||||
var ec := 10000;//Чем больше элементов - тем больше преимущество BlockFileOf<T>, потому что он сохраняет их всех сразу.
|
||||
//Но он быстрее даже если сохранять по 1 элементу
|
||||
var ec := 10000;//Чем больше элементов, тем больше преимущество BlockFileOf<T>, потому что он сохраняет их всех сразу.
|
||||
//Но он быстрее даже если сохранять по одному элементу
|
||||
var t1, t2: int64;
|
||||
|
||||
var f1: file of AR;
|
||||
|
|
@ -295,10 +276,14 @@ begin
|
|||
|
||||
end;
|
||||
|
||||
begin//Уберите флажок "Debug версия" в сервис>>настройки>>опции компиляции и запускайте по Shift+F9, иначе не честно
|
||||
begin
|
||||
// Уберите флажок "Debug версия" в "Сервис>>Настройки>>Опции компиляции" и запускайте по Shift+F9, иначе отладка может неравномерно влиять на результаты
|
||||
|
||||
TestIntegrity;//можно закомментировать
|
||||
// Тест на отсутствие ошибок. Можно убрать
|
||||
TestIntegrity;
|
||||
|
||||
// Тест скорости
|
||||
// Он бесконечный, чем дольше тестируется - тем более усреднённые, а значит и более точные результаты
|
||||
TestSpeed;
|
||||
|
||||
end.
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
//*****************************************************************************************************\\
|
||||
|
||||
//*****************************************************************************************************\\
|
||||
// Copyright (©) Cergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
|
||||
// This code is distributed under the Unlicense
|
||||
// For details see LICENSE.md file or this:
|
||||
// https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/blob/master/LICENSE.md
|
||||
// For details see LICENSE file or this:
|
||||
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
|
||||
//*****************************************************************************************************\\
|
||||
// Copyright (©) Сергей Латченко ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
|
||||
// Этот код распространяется под Unlicense
|
||||
// Для деталей смотрите в файл LICENSE.md или это:
|
||||
// https://github.com/SunSerega/PascalABC.Net-BlockFileOfT/blob/master/LICENSE.md
|
||||
// Этот код распространяется с лицензией Unlicense
|
||||
// Подробнее в файле LICENSE или тут:
|
||||
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
|
||||
//*****************************************************************************************************\\
|
||||
|
||||
///Модуль, содержащий тип BlockFileOf<T>
|
||||
|
|
@ -27,8 +28,6 @@ type
|
|||
protected fi: FileInfo;
|
||||
protected _offset: int64;
|
||||
protected str: FileStream;
|
||||
protected bw: BinaryWriter;
|
||||
protected br: BinaryReader;
|
||||
|
||||
protected linked := new List<BlockFileBase>;
|
||||
|
||||
|
|
@ -45,17 +44,18 @@ type
|
|||
///Это даёт значительное преимущество по скорости, но ограничивает типы, которые могут быть использованы в качестве полей типа <T>
|
||||
///
|
||||
///Это значит, что поля записи T и всех вложенных записей не могут быть:
|
||||
/// - Указателями
|
||||
/// - Ссылочными типами (классами / динамическими массивами)
|
||||
/// - Особоопасными в .NET типами. Например, char и System.DateTime
|
||||
/// - Указателями
|
||||
/// - Ссылочными типами (классами / динамическими массивами)
|
||||
///Однако, эти ограничения можно обойти, об этом в справке
|
||||
BlockFileOf<T> = class(BlockFileBase)
|
||||
BlockFileOf<T> = class(BlockFileBase) where T: record;
|
||||
|
||||
{$region Internal}
|
||||
|
||||
private static sz: integer;
|
||||
|
||||
private static procedure TestForRefT(tt: System.Type);
|
||||
begin
|
||||
if tt.IsClass then
|
||||
if tt.IsClass and not (tt=typeof(pointer)) then
|
||||
begin
|
||||
MessageBox(new System.IntPtr(nil),
|
||||
$'Тип {tt} ссылочный.{#10}Ссылочные типы нельзя сохранять в типизированный файл.{#10}Нажмите OK для выхода.',
|
||||
|
|
@ -72,7 +72,7 @@ type
|
|||
System.Reflection.BindingFlags.NonPublic
|
||||
) do
|
||||
if not fi.IsLiteral then
|
||||
if fi.FieldType <> tt then//integer имеет поле типа integer, без этой строчки StackOwerflow
|
||||
if fi.FieldType <> tt then // тип integer имеет поле типа integer, без этой строчки StackOverflowException
|
||||
TestForRefT(fi.FieldType);
|
||||
end;
|
||||
|
||||
|
|
@ -80,30 +80,13 @@ type
|
|||
try
|
||||
TestForRefT(typeof(T));
|
||||
|
||||
(*
|
||||
try
|
||||
var a := new T[0];
|
||||
GCHandle.Alloc(a,GCHandleType.Pinned).Free;
|
||||
except
|
||||
on e: System.ArgumentException do
|
||||
begin
|
||||
MessageBox(new System.IntPtr(nil),
|
||||
$'.Net не принимает какой то из типов полей записи, для которой вы создали BlockFileOf<T>{#10}Говорит - его нельзя превратить в набор байт{#10}Нажмите OK для выхода.',
|
||||
$'Тип T содержет ссылочные типы',
|
||||
$10
|
||||
);
|
||||
Halt(-1);
|
||||
end;
|
||||
end;
|
||||
(**)
|
||||
|
||||
sz := Marshal.SizeOf(typeof(T));
|
||||
sz := Marshal.SizeOf&<T>;
|
||||
except
|
||||
on e:Exception do
|
||||
begin
|
||||
MessageBox(new System.IntPtr(nil),
|
||||
e.ToString,
|
||||
$'При инициализации BlockFileOf<{typeof(T)}> произошла ошибка',
|
||||
$'BlockFileOf<{typeof(T)}> не может инициализироваться:',
|
||||
$10
|
||||
);
|
||||
Halt(-1);
|
||||
|
|
@ -126,7 +109,9 @@ type
|
|||
private function GetPosByte: int64;
|
||||
private procedure SetPosByte(pos: int64);
|
||||
|
||||
private function InternalReadLazy(c: integer; start_pos: int64):sequence of T;
|
||||
{$endregion Internal}
|
||||
|
||||
{$region constructor's}
|
||||
|
||||
///Инициализирует переменную файла, не привязывая её к файлу на диске
|
||||
public constructor := exit;
|
||||
|
|
@ -136,11 +121,11 @@ type
|
|||
Assign(fname);
|
||||
|
||||
///Инициализирует переменную файла, привязывая её к файлу с именем fname
|
||||
///Устанавливает значение смещения от начала в байтах на offset
|
||||
///Устанавливает значение смещения от начала файла в байтах на offset
|
||||
public constructor(fname: string; offset: int64) :=
|
||||
Assign(fname, offset);
|
||||
|
||||
///- constructor BlockFileOf<>(f:BlockFileOf<>);
|
||||
///- constructor BlockFileOf<>(f: BlockFileOf<>);
|
||||
///Инициализирует новую переменную, создавая связку с заданной переменной
|
||||
///После вызова этого конструктора переданная и созданная переменные будут использовать общий файловый поток, но записывать разные типы данных (у них может быть разный T)
|
||||
///Это значит, что переменная, которую передали в конструктор, уже должна иметь открытый файловый поток
|
||||
|
|
@ -148,6 +133,10 @@ type
|
|||
public constructor(f: BlockFileBase) :=
|
||||
Link(f);
|
||||
|
||||
{$endregion constructor's}
|
||||
|
||||
{$region property's}
|
||||
|
||||
///Возвращает или задаёт размер блока из одного элемента в байтах
|
||||
///Задавать это свойство не рекомендуется
|
||||
public property TSize: integer read integer(sz) write sz := value;
|
||||
|
|
@ -172,17 +161,17 @@ type
|
|||
///Определяет, существует ли файл
|
||||
public property Exists: boolean read GetExists;
|
||||
|
||||
///Возвращает номер текущего элемета в файле (нумеруя с 0)
|
||||
///Возвращает или задаёт номер текущего элемета в файле (нумеруя с 0)
|
||||
public property Pos: int64 read GetPos write SetPos;
|
||||
|
||||
///Возвращает номер текущего байта от начала файла (нумеруя с 0)
|
||||
///Возвращает или задаёт номер текущего байта от начала файла (нумеруя с 0)
|
||||
public property PosByte: int64 read GetPosByte write SetPosByte;
|
||||
|
||||
///Определяет, привязана ли переменная к файлу
|
||||
public property Assigned: boolean read fi <> nil;
|
||||
public property Assigned: boolean read fi<>nil;
|
||||
|
||||
///Определяет, открыт ли файл
|
||||
public property Opened: boolean read str <> nil;
|
||||
public property Opened: boolean read str<>nil;
|
||||
|
||||
///Определяет, достигнут ли конец файла
|
||||
public property EOF: boolean read ByteSize-PosByte < sz;
|
||||
|
|
@ -191,17 +180,13 @@ type
|
|||
///Внимание! Любое действие, связанное с изменением данного потока файла приведёт к неожиданным последствиям, используйте его только если знаете, что вы делаете
|
||||
public property BaseStream: FileStream read str;
|
||||
|
||||
///Возвращает BinaryWriter текущего файла (или nil если файл не открыт)
|
||||
///Внимание! Любое действие, связанное с изменением основного потока файла приведёт к неожиданным последствиям, используйте его только если знаете, что вы делаете
|
||||
public property BinWriter: BinaryWriter read bw;
|
||||
|
||||
///Возвращает BinaryReader текущего файла (или nil если файл не открыт)
|
||||
///Внимание! Любое действие, связанное с изменением основного потока файла приведёт к неожиданным последствиям, используйте его только если знаете, что вы делаете
|
||||
public property BinReader: BinaryReader read br;
|
||||
|
||||
///Возвращает FileInfo текущего файла (или nil, если переменная не привязана к файлу)
|
||||
public property FileInfo: System.IO.FileInfo read fi;
|
||||
|
||||
{$endregion property's}
|
||||
|
||||
{$region Setup IO}
|
||||
|
||||
///Привязывает текущий экземпляр BlockFileOf<T> к файлу с именем fname
|
||||
///Привязывать можно и к несуществующим файлам, при открытии на запись будет создан новый файл
|
||||
public procedure Assign(fname: string);
|
||||
|
|
@ -224,7 +209,7 @@ type
|
|||
///При указании иного расположения файл будет перемещён
|
||||
public procedure Rename(NewName: string);
|
||||
|
||||
///Создает файл
|
||||
///Создает (или обнуляет) файл
|
||||
public procedure Rewrite;
|
||||
|
||||
///Привязывает данную переменную к файлу с именем fname и создает (или обнуляет) его
|
||||
|
|
@ -254,20 +239,18 @@ type
|
|||
///Устанавливает смещение от начала файла до начала элементов в байтах на offset
|
||||
public procedure Append(fname: string; offset: int64);
|
||||
|
||||
///Ставит позицию в файле на элемент pos (нумеруя с 0)
|
||||
public procedure Seek(pos: int64) := self.Pos := pos;
|
||||
|
||||
///Ставит файловый курсор на байт pos (нумеруя с 0) от начала файла
|
||||
public procedure SeekByte(pos: int64) := self.PosByte := pos;
|
||||
|
||||
///Записывает все изменения в файл и отчищает внутренние буферы
|
||||
///До вызова Flush или Close все изменения и кеш хранятся в оперативной памяти
|
||||
///Поэтому, если вы проводите операции с большими объёмами памяти, лучше вызывать Flush время от времени
|
||||
///Если вы проводите операции с большими объёмами памяти - рекомендуется вызывать Flush время от времени
|
||||
public procedure Flush;
|
||||
|
||||
///Сохраняет и закрывает файл, если он открыт
|
||||
public procedure Close;
|
||||
|
||||
{$endregion Setup IO}
|
||||
|
||||
{$region Write}
|
||||
|
||||
///Записывает один элемент одним блоком в файл
|
||||
///Переставляет файловый курсор на 1 элемет вперёд
|
||||
public procedure Write(o: T);
|
||||
|
|
@ -296,6 +279,10 @@ type
|
|||
///После каждого элемента переставляет файловый курсор на 1 элемет вперёд
|
||||
public procedure Write(o: sequence of T; from, count: integer);
|
||||
|
||||
{$endregion Write}
|
||||
|
||||
{$region Read}
|
||||
|
||||
///Читает один элемент из файла одним блоком
|
||||
///Переставляет файловый курсор на 1 элемет вперёд
|
||||
public function Read: T;
|
||||
|
|
@ -308,16 +295,45 @@ type
|
|||
///И переставляет файловый курсор на count элеметов вперёд
|
||||
public function Read(start_elm, count: integer): array of T;
|
||||
|
||||
///Читает один элемент из файла одним блоком и записывает в уже существующую переменную
|
||||
///Переставляет файловый курсор на 1 элемет вперёд
|
||||
public procedure Read(var o: T);
|
||||
|
||||
///Читает o.Length элементов из файла одним блоком и записывает их в уже существующий массив
|
||||
///Переставляет файловый курсор на o.Length элеметов вперёд
|
||||
public procedure Read(o: array of T);
|
||||
|
||||
///Читает count элементов одним блоком, начиная с элемента start_elm
|
||||
///Записывает их в массив o начиная с индекса arr_offset
|
||||
///И переставляет файловый курсор на count элеметов вперёд
|
||||
public procedure Read(o: array of T; start_elm, arr_offset, count: integer);
|
||||
|
||||
private function InternalReadLazy(c: integer; start_pos: int64): sequence of T;
|
||||
|
||||
///Возвращает ленивую последовательность из count элементов
|
||||
///После завершения чтения курсор окажется на последнем элементе возвращаемой последовательности
|
||||
///Каждый раз чтение будет происходить, начиная с элемента, на котором в данный момент стоит файловый курсор
|
||||
///Перед чтением каждого элемента файловый курсор переставляется на start_elm+n
|
||||
///Где start_elm - значение Pos на момент вызова ReadLazy, а n - количество уже считанных элементов
|
||||
public function ReadLazy(count: integer): sequence of T := InternalReadLazy(count, PosByte);
|
||||
|
||||
///Возвращает ленивую последовательность из count элементов, начиная с элемента start_elm
|
||||
///После завершения чтения курсор окажется на последнем элементе возвращаемой последовательности
|
||||
///Каждый раз чтение будет происходить, начиная с элемента start_elm
|
||||
///Перед чтением каждого элемента файловый курсор переставляется на start_elm+n
|
||||
///Где n - количество уже считанных элементов
|
||||
public function ReadLazy(start_elm, count: integer): sequence of T := InternalReadLazy(count, _offset + start_elm*sz);
|
||||
|
||||
{$endregion Read}
|
||||
|
||||
{$region Utils}
|
||||
|
||||
///Ставит позицию в файле на элемент pos (нумеруя с 0)
|
||||
///Рекомендуется использовать свойство Pos вместо данного метода
|
||||
public procedure Seek(pos: int64) := self.Pos := pos;
|
||||
|
||||
///Ставит файловый курсор на байт pos (нумеруя с 0) от начала файла
|
||||
///Рекомендуется использовать свойство PosByte вместо данного метода
|
||||
public procedure SeekByte(pos: int64) := self.PosByte := pos;
|
||||
|
||||
///Возвращает ленивую последовательность из блоков-массивов с элементами типа T
|
||||
///Каждый блок хранит такое количество байт, которое не превышает 4 КБ
|
||||
///После прочтения каждого блока файловый корсор будет переставлен на его конец
|
||||
|
|
@ -337,6 +353,8 @@ type
|
|||
Close;
|
||||
end;
|
||||
|
||||
{$endregion Utils}
|
||||
|
||||
end;
|
||||
|
||||
{$region Exception's}
|
||||
|
|
@ -352,11 +370,11 @@ type
|
|||
end;
|
||||
FileNotClosedException = class(Exception)
|
||||
constructor(fname:string) :=
|
||||
inherited Create($'Файл {fname} открыт, закройте его методом Close перед тем как продолжать');
|
||||
inherited Create($'Файл {fname} открыт, закройте его методом Close перед тем как продолжить');
|
||||
end;
|
||||
CannotReadAfterEOF = class(Exception)
|
||||
constructor :=
|
||||
inherited Create($'Нельзя читать за пределами файла');
|
||||
inherited Create($'Нельзя читать после конца потока');
|
||||
end;
|
||||
|
||||
{$endregion Exception's}
|
||||
|
|
@ -377,8 +395,6 @@ begin
|
|||
end;
|
||||
|
||||
self.str := f.str;
|
||||
self.br := f.br;
|
||||
self.bw := f.bw;
|
||||
self.fi := new FileInfo(f.fi.FullName);
|
||||
|
||||
end;
|
||||
|
|
@ -407,7 +423,7 @@ end;
|
|||
function BlockFileOf<T>.GetFullName: string;
|
||||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
//fi.Refresh;//А тут не надо
|
||||
//fi.Refresh; // А тут не надо
|
||||
Result := fi.FullName;
|
||||
end;
|
||||
|
||||
|
|
@ -417,12 +433,11 @@ function BlockFileOf<T>.GetByteFileSize: int64;
|
|||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str <> nil then
|
||||
Result := str.Length else
|
||||
begin
|
||||
Result := str.Length;
|
||||
exit;
|
||||
fi.Refresh;
|
||||
Result := fi.Length;
|
||||
end;
|
||||
fi.Refresh;
|
||||
Result := fi.Length;
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.SetByteFileSize(size: int64);
|
||||
|
|
@ -477,8 +492,6 @@ begin
|
|||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str <> nil then raise new FileNotClosedException(fi.FullName);
|
||||
str := fi.Open(mode);
|
||||
bw := new BinaryWriter(str);
|
||||
br := new BinaryReader(str);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Delete;
|
||||
|
|
@ -580,8 +593,6 @@ begin
|
|||
UnLink else
|
||||
str.Close;
|
||||
str := nil;
|
||||
br := nil;
|
||||
bw := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -589,6 +600,16 @@ end;
|
|||
|
||||
{$endregion Setup IO}
|
||||
|
||||
{$region IO Utils}
|
||||
|
||||
procedure CopyMem<T1,T2>(var o1: T1; var o2: T2; count: integer) :=
|
||||
System.Buffer.MemoryCopy(
|
||||
@o1, @o2,
|
||||
count, count
|
||||
);
|
||||
|
||||
{$endregion IO Utils}
|
||||
|
||||
{$region Write}
|
||||
|
||||
procedure BlockFileOf<T>.Write(o: T);
|
||||
|
|
@ -596,17 +617,8 @@ begin
|
|||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
var a := new byte[sz];
|
||||
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
System.Buffer.MemoryCopy(
|
||||
@o,
|
||||
gc_hnd.AddrOfPinnedObject.ToPointer,
|
||||
sz,sz
|
||||
);
|
||||
finally
|
||||
gc_hnd.Free;
|
||||
end;
|
||||
bw.Write(a);
|
||||
CopyMem(o,a[0],sz);
|
||||
str.Write(a,0,sz);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Write(params o: array of T);
|
||||
|
|
@ -616,58 +628,31 @@ begin
|
|||
|
||||
var bl := sz*o.Length;
|
||||
var a := new byte[bl];
|
||||
var gc_hnd1 := GCHandle.Alloc(o, GCHandleType.Pinned);
|
||||
try
|
||||
var gc_hnd2 := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
System.Buffer.MemoryCopy(
|
||||
gc_hnd1.AddrOfPinnedObject.ToPointer,
|
||||
gc_hnd2.AddrOfPinnedObject.ToPointer,
|
||||
bl,bl
|
||||
);
|
||||
finally
|
||||
gc_hnd2.Free;
|
||||
end;
|
||||
finally
|
||||
gc_hnd1.Free;
|
||||
end;
|
||||
bw.Write(a);
|
||||
CopyMem(o[0],a[0],bl);
|
||||
str.Write(a,0,bl);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Write(o: ICollection<T>);
|
||||
type TArr = array of T;
|
||||
begin
|
||||
if o is TArr(var a) then
|
||||
begin
|
||||
Write(a);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
|
||||
var a := new byte[sz*o.Count];
|
||||
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
var hnd := gc_hnd.AddrOfPinnedObject;
|
||||
foreach var el in o do
|
||||
begin
|
||||
System.Buffer.MemoryCopy(
|
||||
@el,
|
||||
hnd.ToPointer,
|
||||
sz,sz
|
||||
);
|
||||
System.IntPtr.Add(hnd, sz);
|
||||
end;
|
||||
finally
|
||||
gc_hnd.Free;
|
||||
var bl := sz*o.Count;
|
||||
var a := new byte[bl];
|
||||
|
||||
var p := 0;
|
||||
var enm: IEnumerator<T> := o.GetEnumerator();
|
||||
while enm.MoveNext do
|
||||
begin
|
||||
var v := enm.Current;
|
||||
CopyMem(v,a[p],sz);
|
||||
p += sz;
|
||||
end;
|
||||
bw.Write(a);
|
||||
|
||||
str.Write(a,0,bl);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Write(o: sequence of T) :=
|
||||
if o is ICollection<T>(var c) then
|
||||
Write(c) else
|
||||
foreach var el in o do
|
||||
Write(el);
|
||||
|
||||
|
|
@ -675,65 +660,37 @@ procedure BlockFileOf<T>.Write(o: array of T; from, count: integer);
|
|||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if from+count > o.Length then raise new System.IndexOutOfRangeException;
|
||||
|
||||
var bl := sz*count;
|
||||
var a := new byte[bl];
|
||||
var gc_hnd1 := GCHandle.Alloc(o, GCHandleType.Pinned);
|
||||
try
|
||||
var gc_hnd2 := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
System.Buffer.MemoryCopy(
|
||||
System.IntPtr.Add(gc_hnd1.AddrOfPinnedObject, from * sz).ToPointer,
|
||||
gc_hnd2.AddrOfPinnedObject.ToPointer,
|
||||
bl,bl
|
||||
);
|
||||
finally
|
||||
gc_hnd2.Free;
|
||||
end;
|
||||
finally
|
||||
gc_hnd1.Free;
|
||||
end;
|
||||
bw.Write(a);
|
||||
CopyMem(o[from],a[0],bl);
|
||||
str.Write(a,0,bl);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Write(o: ICollection<T>; from, count: integer);
|
||||
type TArr = array of T;
|
||||
begin
|
||||
if o is TArr(var a) then
|
||||
begin
|
||||
Write(a, from, count);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if from+count > o.Count then raise new System.IndexOutOfRangeException;
|
||||
|
||||
var a := new byte[sz*o.Count];
|
||||
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
var hnd := gc_hnd.AddrOfPinnedObject;
|
||||
foreach var el in o do
|
||||
if from > 0 then from -= 1 else
|
||||
if count > 0 then
|
||||
begin
|
||||
System.Buffer.MemoryCopy(
|
||||
@el,
|
||||
hnd.ToPointer,
|
||||
sz,sz
|
||||
);
|
||||
hnd := System.IntPtr.Add(hnd, sz);
|
||||
count -= 1;
|
||||
end;
|
||||
finally
|
||||
gc_hnd.Free;
|
||||
var bl := sz*count;
|
||||
var a := new byte[bl];
|
||||
|
||||
var p := 0;
|
||||
var enm: IEnumerator<T> := o.Skip(from).Take(count).GetEnumerator;
|
||||
while enm.MoveNext do
|
||||
begin
|
||||
var v := enm.Current;
|
||||
CopyMem(v,a[p],sz);
|
||||
p += sz;
|
||||
end;
|
||||
bw.Write(a);
|
||||
|
||||
str.Write(a,0,bl);
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Write(o: sequence of T; from, count: integer) :=
|
||||
if o is ICollection<T>(var c) then
|
||||
Write(c,from,count) else
|
||||
Write(o.Skip(from).Take(count));
|
||||
Write(o.Skip(from).Take(count));
|
||||
|
||||
{$endregion Write}
|
||||
|
||||
|
|
@ -745,17 +702,10 @@ begin
|
|||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - str.Position < sz then raise new CannotReadAfterEOF;
|
||||
|
||||
var a := br.ReadBytes(sz);
|
||||
var gc_hnd := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
System.Buffer.MemoryCopy(
|
||||
gc_hnd.AddrOfPinnedObject.ToPointer,
|
||||
@Result,
|
||||
sz,sz
|
||||
);
|
||||
finally
|
||||
gc_hnd.Free;
|
||||
end;
|
||||
var a := new byte[sz];
|
||||
str.Read(a,0,sz);
|
||||
CopyMem(a[0],Result,sz);
|
||||
|
||||
end;
|
||||
|
||||
function BlockFileOf<T>.Read(count: integer): array of T;
|
||||
|
|
@ -765,48 +715,90 @@ begin
|
|||
if str.Length - str.Position < sz*count then raise new CannotReadAfterEOF;
|
||||
|
||||
var bl := sz*count;
|
||||
var a := br.ReadBytes(bl);
|
||||
var a := new byte[bl];
|
||||
str.Read(a,0,bl);
|
||||
Result := new T[count];
|
||||
var gc_hnd1 := GCHandle.Alloc(a, GCHandleType.Pinned);
|
||||
try
|
||||
var gc_hnd2 := GCHandle.Alloc(Result, GCHandleType.Pinned);
|
||||
try
|
||||
System.Buffer.MemoryCopy(
|
||||
gc_hnd1.AddrOfPinnedObject.ToPointer,
|
||||
gc_hnd2.AddrOfPinnedObject.ToPointer,
|
||||
bl,bl
|
||||
);
|
||||
finally
|
||||
gc_hnd2.Free;
|
||||
end;
|
||||
finally
|
||||
gc_hnd1.Free;
|
||||
end;
|
||||
CopyMem(a[0],Result[0],bl);
|
||||
|
||||
end;
|
||||
|
||||
function BlockFileOf<T>.Read(start_elm, count:integer): array of T;
|
||||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - str.Position < sz*count then raise new CannotReadAfterEOF;
|
||||
|
||||
Pos := start_elm;
|
||||
Result := Read(count);
|
||||
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Read(var o: T);
|
||||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - str.Position < sz then raise new CannotReadAfterEOF;
|
||||
|
||||
var a := new byte[sz];
|
||||
str.Read(a,0,sz);
|
||||
CopyMem(a[0],o,sz);
|
||||
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Read(o: array of T);
|
||||
begin
|
||||
var bl := sz*o.Length;
|
||||
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - str.Position < bl then raise new CannotReadAfterEOF;
|
||||
|
||||
var a := new byte[bl];
|
||||
str.Read(a,0,bl);
|
||||
CopyMem(a[0],o[0],bl);
|
||||
|
||||
end;
|
||||
|
||||
procedure BlockFileOf<T>.Read(o: array of T; start_elm, arr_offset, count: integer);
|
||||
begin
|
||||
var bl := sz*count;
|
||||
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - str.Position < bl then raise new CannotReadAfterEOF;
|
||||
if arr_offset+count > o.Length then raise new System.IndexOutOfRangeException;
|
||||
|
||||
Pos := start_elm;
|
||||
var a := new byte[bl];
|
||||
str.Read(a,0,bl);
|
||||
CopyMem(a[0],o[start_elm],bl);
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Read}
|
||||
|
||||
{$region Utils}
|
||||
|
||||
function BlockFileOf<T>.InternalReadLazy(c: integer; start_pos: int64): sequence of T;
|
||||
begin
|
||||
if fi = nil then raise new FileNotAssignedException;
|
||||
if str = nil then raise new FileNotOpenedException(fi.FullName);
|
||||
if str.Length - start_pos < sz*c then raise new CannotReadAfterEOF;
|
||||
|
||||
for var i := 0 to c-1 do
|
||||
loop c do
|
||||
begin
|
||||
PosByte := start_pos + i*sz;
|
||||
PosByte := start_pos;
|
||||
yield Read;
|
||||
start_pos += sz;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function BlockFileOf<T>.ToSeqBlocks(blocks_size: integer): sequence of array of T;
|
||||
begin
|
||||
var c := blocks_size div sz;
|
||||
var i := 0;
|
||||
|
||||
while true do
|
||||
begin
|
||||
var left := Size - i;
|
||||
|
|
@ -825,20 +817,24 @@ begin
|
|||
yield Read(c);
|
||||
i += c;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function BlockFileOf<T>.ToSeq: sequence of T;
|
||||
begin
|
||||
var i := 0;
|
||||
|
||||
while not EOF do
|
||||
begin
|
||||
Pos := i;
|
||||
yield Read;
|
||||
i += 1;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Read}
|
||||
{$endregion Utils}
|
||||
|
||||
end.
|
||||
Loading…
Reference in a new issue