diff --git a/InstallerSamples/BlockFileOfT/FileDatabase/FileArray.pas b/InstallerSamples/BlockFileOfT/FileDatabase/FileArray.pas index a134d882f..4591f2a3d 100644 --- a/InstallerSamples/BlockFileOfT/FileDatabase/FileArray.pas +++ b/InstallerSamples/BlockFileOfT/FileDatabase/FileArray.pas @@ -1,171 +1,174 @@ -///Модуль с массивом, хранящим данные в файле -unit FileArray; - -interface - -uses BlockFileOfT; - -type - ///Массив, хранящим данные в файле - ///На много медленнее обычного, но хранит на много больший объём данных - ///T может быть только запись, не содержащей ссылочные поля (указатели, классы и т.п.) - FileArr = sealed class - where T: record; - - private f: BlockFileOf; - ///Автоматически отчищает кэш после каждого чтения и записи - 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 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(fname); - f.Open(mode); - end; - ///Использует заданный файл как основу для массива - ///Если файл не открыт - его откроет в режиме System.IO.FileMode.OpenOrCreate - public constructor(&file: BlockFileOf); - begin - f := &file; - if not f.Opened then - f.Open(System.IO.FileMode.OpenOrCreate); - end; - ///Открывает заданный файл в режиме mode и использует его как основу для массива - public constructor(&file: BlockFileOf; 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.GetItem32(i:integer) := GetItem64(i); - -function FileArr.GetSection32(from,c:integer) := GetSection64(from,c); - -function FileArr.GetSectionLazy32(from,c:integer) := GetSectionLazy64(from,c); - -procedure FileArr.SetItem32(i:integer; o:T) := SetItem64(i,o); - -procedure FileArr.SetSection32(from,c:integer; o:array of T) := SetSection64(from,c,o); - -procedure FileArr.SetSectionLazy32(from,c:integer; o:sequence of T) := SetSectionLazy64(from,c,o); - -function FileArr.GetItem64(i:int64):T; -begin - f.Seek(i); - Result := f.Read; - if AutoFlush then f.BaseStream.Flush; -end; - -function FileArr.GetSection64(from,c:int64):array of T; -begin - f.Seek(from); - Result := f.Read(c); - if AutoFlush then f.BaseStream.Flush; -end; - -function FileArr.GetSectionLazy64(from,c:int64):sequence of T; -begin - f.Seek(from); - Result := f.ReadLazy(c); - if AutoFlush then f.BaseStream.Flush; -end; - -procedure FileArr.SetItem64(i:int64; o:T); -begin - f.Seek(i); - f.Write(o); - if AutoFlush then f.BaseStream.Flush; -end; - -procedure FileArr.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.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 = sealed class + where T: record; + + private f: BlockFileOf; + ///Автоматически очищает кэш после каждого чтения и записи + 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 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(fname); + f.Open(mode); + end; + + ///Использует заданный файл как основу для массива + ///Если файл не открыт - его откроет в режиме System.IO.FileMode.OpenOrCreate + public constructor(&file: BlockFileOf); + begin + f := &file; + if not f.Opened then + f.Open(System.IO.FileMode.OpenOrCreate); + end; + + ///Открывает заданный файл в режиме mode и использует его как основу для массива + public constructor(&file: BlockFileOf; 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.GetItem32(i: integer) := GetItem64(i); + +function FileArr.GetSection32(from, c: integer) := GetSection64(from, c); + +function FileArr.GetSectionLazy32(from, c: integer) := GetSectionLazy64(from, c); + +procedure FileArr.SetItem32(i: integer; o: T) := SetItem64(i, o); + +procedure FileArr.SetSection32(from, c: integer; o: array of T) := SetSection64(from, c, o); + +procedure FileArr.SetSectionLazy32(from, c: integer; o: sequence of T) := SetSectionLazy64(from, c, o); + +function FileArr.GetItem64(i: int64): T; +begin + f.Seek(i); + Result := f.Read; + if AutoFlush then f.BaseStream.Flush; +end; + +function FileArr.GetSection64(from, c: int64): array of T; +begin + f.Seek(from); + Result := f.Read(c); + if AutoFlush then f.BaseStream.Flush; +end; + +function FileArr.GetSectionLazy64(from, c: int64): sequence of T; +begin + f.Seek(from); + Result := f.ReadLazy(c); + if AutoFlush then f.BaseStream.Flush; +end; + +procedure FileArr.SetItem64(i: int64; o: T); +begin + f.Seek(i); + f.Write(o); + if AutoFlush then f.BaseStream.Flush; +end; + +procedure FileArr.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.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. \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/FileDatabase/База данных на файле.pas b/InstallerSamples/BlockFileOfT/FileDatabase/База данных на файле.pas index 74b813fe8..9eb5ec277 100644 --- a/InstallerSamples/BlockFileOfT/FileDatabase/База данных на файле.pas +++ b/InstallerSamples/BlockFileOfT/FileDatabase/База данных на файле.pas @@ -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; - public Data: FileArr; - - public constructor := exit; - - public constructor(fname:string; version:byte; DatabaseType:string); - begin - self.version := version; - self.DatabaseType := DatabaseType; - - self.BlockFile := new BlockFileOf(fname); - self.BlockFile.Offset := 5; - self.BlockFile.Open(System.IO.FileMode.Create); - - self.Data := new FileArr(self.BlockFile); - end; - - public class function Load(fname:string): Database; - begin - Result := new Database; - - Result.BlockFile := new BlockFileOf(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(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; + public Data: FileArr; + + public constructor := exit; + + public constructor(fname: string; version: byte; DatabaseType: string); + begin + self.version := version; + self.DatabaseType := DatabaseType; + + self.BlockFile := new BlockFileOf(fname); + self.BlockFile.Offset := HeaderSize; + self.BlockFile.Open(System.IO.FileMode.Create); + + self.Data := new FileArr(self.BlockFile); + end; + + public class function Load(fname: string): Database; + begin + Result := new Database; + + Result.BlockFile := new BlockFileOf(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(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. \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/Дополнительно.pas b/InstallerSamples/BlockFileOfT/Дополнительно.pas index b3d80013a..156000c6e 100644 --- a/InstallerSamples/BlockFileOfT/Дополнительно.pas +++ b/InstallerSamples/BlockFileOfT/Дополнительно.pas @@ -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('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; - 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('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; + // f2.TSize это то же самое, что sizeof(r2) + // У меня 16, хотя sizeof(byte)=1 + sizeof(int64)=8, то есть должно быть 9? + // Это потому, что на многих процессорах добавляет отступы (так легче читать/записывать значения) в некоторые записи, так что будьте осторожны + // Это также хорошая причина не пользоваться бездумно BaseStream - потому что на разных компьютерах один и тот же код может работать по-разному + Writeln(f2.TSize); + + var f3 := new BlockFileOf; + // А вот теперь размер 9, потому что мы явно указали что отступ нам не нужен + // Конечно, теперь чтение/запись i может быть медленнее на некоторых процессорах, отступ всё же не для красоты был + Writeln(f3.TSize); + end. \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/Основы.pas b/InstallerSamples/BlockFileOfT/Основы.pas index fb948144e..4c134b24c 100644 --- a/InstallerSamples/BlockFileOfT/Основы.pas +++ b/InstallerSamples/BlockFileOfT/Основы.pas @@ -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; - //f := new BlockFileOf;//В отличии от file of T - всегда надо инициализировать перед тем как использовать - //f.Assign('temp.bin'); - var f := new BlockFileOf('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 нету режима чтения и режима записи - //Они объеденены, поэтому не надо закрывать файл чтоб записать что то после чтения - - 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; +// f := new BlockFileOf; // В отличии от file of T - BlockFileOf всегда надо инициализировать перед тем как использовать +// f.Assign('temp.bin'); + var f := new BlockFileOf('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 нету режима чтения и режима записи + // Они объеденены, поэтому не надо закрывать файл чтобы записать что-то после чтения + 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. \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/Справка.html b/InstallerSamples/BlockFileOfT/Справка.html index 5134ae4d2..b2af63627 100644 --- a/InstallerSamples/BlockFileOfT/Справка.html +++ b/InstallerSamples/BlockFileOfT/Справка.html @@ -1,22 +1,22 @@ - - - - - - - - - - - Выберете раздел: - -
- Если найдёте что то неправильное/ошибку (в том числе грамматическую) в справке - пишите сюда. - - + + + + + + + + + + + Выберете раздел: + +
+ Если найдёте что-то неправильное или ошибку (в том числе и грамматическую) в справке - пишите сюда. + + \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/СправкаData/RefTSaving.html b/InstallerSamples/BlockFileOfT/СправкаData/RefTSaving.html index abdba1b6c..a1470c91e 100644 --- a/InstallerSamples/BlockFileOfT/СправкаData/RefTSaving.html +++ b/InstallerSamples/BlockFileOfT/СправкаData/RefTSaving.html @@ -1,491 +1,134 @@ - - - - - - - - - - - - - - <=
-
- Сохранение ссылочных и особых типов в блочный файл.
-
- - -

В чём проблема особых типов

- - - - -

В чём проблема ссылочных типов

- - - - -

Массивы (статичные)

- - - - -

Использование сериализаторов

- - - - - + + + + + + + + + + + + + + <=
+
+ Сохранение ссылочных типов в блочный файл.
+
+ + + +

В чём проблема ссылочных типов

+ + + + +

Размерные массивы и строки

+ + + + + + \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/СправкаData/WhyOffset.html b/InstallerSamples/BlockFileOfT/СправкаData/WhyOffset.html index 460fb6fa0..2e69dbd19 100644 --- a/InstallerSamples/BlockFileOfT/СправкаData/WhyOffset.html +++ b/InstallerSamples/BlockFileOfT/СправкаData/WhyOffset.html @@ -1,108 +1,110 @@ - - - - - - - - - - - - - - <=
-
-
- - -

Что такое "Offset"

- - - - -

Заголовки

- - - - - + + + + + + + + + + + + + + <=
+
+
+ + +

Что такое "Offset"

+ + + + +

Заголовки

+ + + + + \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/СправкаData/main.html b/InstallerSamples/BlockFileOfT/СправкаData/main.html index fd5d2c6ff..546c5940f 100644 --- a/InstallerSamples/BlockFileOfT/СправкаData/main.html +++ b/InstallerSamples/BlockFileOfT/СправкаData/main.html @@ -1,264 +1,265 @@ - - - - - - - - - - - - - - <=
-
- Перед тем как начать, хоть уточнить:
- Ожидается что у вас есть хоть какое то представление о "file of T".
- Конечно, без того можно разобраться, но будет сложнее. -
- - -

Объявление

- - - - -

Основы записи/чтения

- - - - -

Перечисление всех элементов в файле

- - - - -

Основное преимущество: сохранение записей (record)

- - - - - + + + + + + + + + + + + + + <=
+
+ Основной упор данной страницы - сравнение "file of T" и "BlockFileOf<T>".
+ Но она также подходит и для обучения основам "BlockFileOf<T>". +
+ + +

Объявление

+ + + + +

Основы записи/чтения

+ + + + +

Перечисление всех элементов в файле

+ + + + +

Основное преимущество: сохранение записей (record)

+ + + + + \ No newline at end of file diff --git a/InstallerSamples/BlockFileOfT/Сравнение скорости.pas b/InstallerSamples/BlockFileOfT/Сравнение скорости.pas index 038b89f9c..a6c377d4d 100644 --- a/InstallerSamples/BlockFileOfT/Сравнение скорости.pas +++ b/InstallerSamples/BlockFileOfT/Сравнение скорости.pas @@ -1,116 +1,97 @@ uses BlockFileOfT; +// Эта процедура позволяет копировать память между содержимым массива и другим массивом/стеком +// При этом не волнуясь о блокировках массивов в памяти +// Если элемент массива передан как var-параметр, то сборщик мусора не трогает этот массив +procedure CopyMem(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.LengthMaxLength, то 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 BR = record - s: CharArr255; - //dt: System.DateTime;//И тут тоже - DateTime это "особый тип", подробнее в справке - dt: int64;//Ну а мы - знаем 1 версию, ту что сейчас, и в ней всё хранится как внутренне поле типа int64 + s: ValueString255; + dt: DateTime; // А BlockFileOf принимает любые размерные типы без ограничений 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 + ///Тут же описаны и преобразования всех особых типов + ///Заметьте, преобразование string[255] к string и назад ничего не копирует и не преобразовывает, если изначальная строка уже была <= 255 символов + ///Поэтому преобразования между ValueString255 и string работают медленнее + ///И даже при этом BlockFileOf всё равно быстрее 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, потому что он сохраняет их всех сразу. - //Но он быстрее даже если сохранять по 1 элементу + var ec := 10000;//Чем больше элементов, тем больше преимущество BlockFileOf, потому что он сохраняет их всех сразу. + //Но он быстрее даже если сохранять по одному элементу 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. \ No newline at end of file diff --git a/bin/Lib/BlockFileOfT.pas b/bin/Lib/BlockFileOfT.pas index 1a8e85a06..061c9c930 100644 --- a/bin/Lib/BlockFileOfT.pas +++ b/bin/Lib/BlockFileOfT.pas @@ -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 @@ -27,8 +28,6 @@ type protected fi: FileInfo; protected _offset: int64; protected str: FileStream; - protected bw: BinaryWriter; - protected br: BinaryReader; protected linked := new List; @@ -45,17 +44,18 @@ type ///Это даёт значительное преимущество по скорости, но ограничивает типы, которые могут быть использованы в качестве полей типа /// ///Это значит, что поля записи T и всех вложенных записей не могут быть: - /// - Указателями - /// - Ссылочными типами (классами / динамическими массивами) - /// - Особоопасными в .NET типами. Например, char и System.DateTime + /// - Указателями + /// - Ссылочными типами (классами / динамическими массивами) ///Однако, эти ограничения можно обойти, об этом в справке - BlockFileOf = class(BlockFileBase) + BlockFileOf = 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{#10}Говорит - его нельзя превратить в набор байт{#10}Нажмите OK для выхода.', - $'Тип T содержет ссылочные типы', - $10 - ); - Halt(-1); - end; - end; - (**) - - sz := Marshal.SizeOf(typeof(T)); + sz := Marshal.SizeOf&; 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 к файлу с именем 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.GetFullName: string; begin if fi = nil then raise new FileNotAssignedException; - //fi.Refresh;//А тут не надо + //fi.Refresh; // А тут не надо Result := fi.FullName; end; @@ -417,12 +433,11 @@ function BlockFileOf.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.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.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(var o1: T1; var o2: T2; count: integer) := +System.Buffer.MemoryCopy( + @o1, @o2, + count, count +); + +{$endregion IO Utils} + {$region Write} procedure BlockFileOf.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.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.Write(o: ICollection); -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 := 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.Write(o: sequence of T) := -if o is ICollection(var c) then - Write(c) else foreach var el in o do Write(el); @@ -675,65 +660,37 @@ procedure BlockFileOf.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.Write(o: ICollection; 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 := 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.Write(o: sequence of T; from, count: integer) := -if o is ICollection(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.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.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.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.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.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.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.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.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. \ No newline at end of file