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; // Size контролирует размер записи. Его можно не указывать, тогда его выберет автоматически // В нашем случае нужен один байт для поля length // А затем место под 255 символов (масимум, который может хранить эта строка) // При этом каждый символ занимает два байта в памяти [StructLayout(LayoutKind.&Explicit, Size= 1 + 255*2 )] ValueString255 = record public [FieldOffset(0)] length: byte; public [FieldOffset(1)] body: char; 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(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 static function operator explicit(s: string): ValueString255; begin Result.length := Min(MaxLength, s.Length); if Result.length=0 then exit; CopyMem(s[1], Result.body, Result.length*2); end; 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 static function operator explicit(s: ValueString255): string := new string(@s.body, 0, s.length); public function ToString: string; override := string(self); end; ///Это будет сохранять в file of T AR = record s: string[255]; //dt: DateTime; // Не даёт ¯\_(ツ)_/¯ dt: int64; // Ну и ладно, в System.DateTime всё хранится в одном поле типа int64 i: integer; ch: char; b: byte; end; ///Это будет сохранять в BlockFileOf BR = record s: ValueString255; dt: DateTime; // А BlockFileOf принимает любые размерные типы без ограничений i: integer; ch: char; b: byte; end; ///Это тип для ввода/вывода ///В нём хранятся входные данные, общие и для file of T, и для BlockFileOf ///Тут же описаны и преобразования всех особых типов ///Заметьте, преобразование string[255] к string и назад ничего не копирует и не преобразовывает, если изначальная строка уже была <= 255 символов ///Поэтому преобразования между ValueString255 и string работают медленнее ///И даже при этом BlockFileOf всё равно быстрее IOR = record s: string; dt: System.DateTime; i: integer; ch: char; b: byte; static function operator explicit(a: IOR): AR; begin Result.s := a.s; Result.dt := a.dt.Ticks; // Ticks возвращает никак не преобразованное значение единственного внутреннего поля DateTime Result.i := a.i; Result.ch := a.ch; Result.b := a.b; end; static function operator explicit(a: IOR): BR; begin Result.s := ValueString255(a.s); // вызываем наш operator explicit Result.dt := a.dt; Result.i := a.i; Result.ch := a.ch; Result.b := a.b; end; static function operator explicit(a: AR): IOR; begin Result.s := a.s; Result.dt := new DateTime(a.dt); // Единственный конструктор DateTime принимающий 1 параметр - принимает кол-во тиков. И напрямую присваивает это значение внутреннему полю Result.i := a.i; Result.ch := a.ch; Result.b := a.b; end; 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))); Result.i := (Random(word.MaxValue) shl 16) + Random(word.MaxValue); Result.ch := ChrAnsi(Random(byte.MaxValue)); Result.b := Random(256); end; public function ToString: string; override := $'IOR( s.Length={s.Length}, dt={dt}, i={i}, ch="{ch}", b={b} )'; end; procedure TestIntegrity1(c: integer); begin var f: file of AR; var test_arr := ArrGen(c, i -> IOR.GetRandom); Rewrite(f, 'temp.bin'); foreach var a in test_arr.Select(a -> AR(a)) do f.Write(a); f.Close; Reset(f); var n_test_arr := f.ReadElements.Select(a -> IOR(a)).ToArray; f.Close; if not test_arr.SequenceEqual(n_test_arr) then begin writeln('тест 1 (file of T) не пройден'); if test_arr.Length <> n_test_arr.Length then writeln($'len: {test_arr.Length} <> {n_test_arr.Length}') else for var i := 0 to test_arr.Length - 1 do if test_arr[i] <> n_test_arr[i] then begin writeln($'el:{#10}{test_arr[i]}{#10}{n_test_arr[i]}{#10}'); end; readln; exit; end; end; procedure TestIntegrity2(c: integer); begin var f := new BlockFileOf
('temp.bin'); var test_arr := ArrGen(c, i -> IOR.GetRandom); f.Rewrite; f.Write(test_arr.ConvertAll(a -> BR(a))); f.Close; f.Reset; var n_test_arr := f.Read(f.Size).ConvertAll(a -> IOR(a)); f.Close; if not test_arr.SequenceEqual(n_test_arr) then begin writeln('тест 2 (BlockFileOf) не пройден'); if test_arr.Length <> n_test_arr.Length then writeln($'len: {test_arr.Length} <> {n_test_arr.Length}') else for var i := 0 to test_arr.Length - 1 do if test_arr[i] <> n_test_arr[i] then begin writeln($'el:{#10}{test_arr[i]}{#10}{n_test_arr[i]}{#10}'); end; readln; exit; end; end; procedure TestIntegrity; begin TestIntegrity1(10000); writeln('тест 1 ok'); TestIntegrity2(10000); writeln('тест 2 ok'); writeln('тесты ok'); end; procedure TestSpeed; begin var sw := new System.Diagnostics.Stopwatch; // Точнее, чем этим, замерить невозможно var lc := 10; var ec := 10000;//Чем больше элементов, тем больше преимущество BlockFileOf, потому что он сохраняет их всех сразу. //Но он быстрее даже если сохранять по одному элементу var t1, t2: int64; var f1: file of AR; Assign(f1, 'temp.bin'); var f2 := new BlockFileOf
('temp.bin'); while true do begin var test_arr := ArrGen(ec, i -> IOR.GetRandom); sw.Restart; loop lc do begin Rewrite(f1); var AR_arr := test_arr.ConvertAll(a -> AR(a)); foreach var a in AR_arr do f1.Write(a); f1.Close; Reset(f1); ArrGen(ec, i -> IOR(f1.Read)); f1.Close; end; sw.Stop; t1 += sw.ElapsedTicks; sw.Restart; loop lc do begin f2.Rewrite; f2.Write(test_arr.ConvertAll(a -> BR(a))); f2.Close; f2.Reset; f2.Read(ec).ConvertAll(a -> IOR(a)); f2.Close; end; sw.Stop; t2 += sw.ElapsedTicks; System.GC.Collect; System.Console.Clear; writeln($'file of T: {t1}'); writeln($'BlockFile: {t2}'); end; end; begin // Уберите флажок "Debug версия" в "Сервис>>Настройки>>Опции компиляции" и запускайте по Shift+F9, иначе отладка может неравномерно влиять на результаты // Тест на отсутствие ошибок. Можно убрать TestIntegrity; // Тест скорости // Он бесконечный, чем дольше тестируется - тем более усреднённые, а значит и более точные результаты TestSpeed; end.