float to int explicitly

Move some samples to Experimental
This commit is contained in:
Mikhalkovich Stanislav 2022-01-17 21:48:58 +03:00
parent 775b363a45
commit 7e472fa16f
17 changed files with 69 additions and 1348 deletions

View file

@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3";
public const string Minor = "8";
public const string Build = "2";
public const string Revision = "3059";
public const string Revision = "3061";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%MINOR%=8
%REVISION%=3059
%REVISION%=3061
%COREVERSION%=2
%MAJOR%=3

View file

@ -1,174 +0,0 @@
///Модуль с массивом, хранящим данные в файле
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.

View file

@ -1,201 +0,0 @@
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.

View file

@ -1,79 +0,0 @@
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.

View file

@ -1,67 +0,0 @@
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.

View file

@ -1,22 +0,0 @@
<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>

View file

@ -1,134 +0,0 @@
<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&ltT>" берёт содержимое переменных одним блоком памяти.<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>

View file

@ -1,110 +0,0 @@
<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&ltElementT>('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&ltHeaderT>(f); // Этот конструктор создаст новый "BlockFileOf&ltT>", у которого будет общий фаловый поток с "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&ltHeaderT>(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>

View file

@ -1,265 +0,0 @@
<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&ltT>".<br>
Но она также подходит и для обучения основам "BlockFileOf&ltT>".
<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&ltT>":</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&ltT>; </t>
<br><t style="margin-left: 10px">f := new BlockFileOf&ltT>; // В отличии от file of T - BlockFileOf&ltT> нужно всегда инициализировать </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&ltT>; </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&ltbyte>('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&ltT>":</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&ltinteger>('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&ltT>" все 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&ltT>":</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&ltinteger>('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&ltT>.ToSeq" работает так же, как и "(file of T).ReadElements" (если не считать более продвинутой защиты от дурака):<br>
Они оба читают файл по одному элементу и после каждого из них проверяют, не достигнут ли конец файла.<br>
<br>
Но в "BlockFileOf&ltT>" есть альтернатива - "ToSeqBlocks".<br>
Эта функция читает всё из файла блоками заданной длины в байтах. Если не задавать длину, данные будут читаться блоками по 4 килобайта.<br>
В нашем случае, "integer" занимает 4 байта в памяти, значит в каждом блоке по 4 КБ поместится 1024 элемента типа "integer"<br>
(Объём типа T можно узнать из свойства "BlockFileOf&ltT>.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&ltinteger>('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&ltr1>('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&ltT>" подходит к этому с другой стороны.<br>
Когда вы первый раз инициализируете переменную типа "BlockFileOf&ltr1>" -<br>
объём в памяти, занимаемый переменными типа "r1", вычисляется и сохраняется во внутреннее статическое поле<br>
(Его можно получить через свойство "TSize")<br>
И когда вызывается функция "Read" - "BlockFileOf&ltT>" читает из файла столько байт, сколько занимают переменные типа "r1",<br>
А потом копирует содержимое полученного массива байт в результат.<br>
<br>
То есть, "file of T" читает три блока, а "BlockFileOf&ltT>" только один.<br>
И когда объём данных одинаковый - количество блоков очень важный фактор.<br>
Поэтому "BlockFileOf&ltT>" будет быстрее.<br>
И размер одного блока не ограничивается размером одной переменной типа "r1".<br>
"Read(10)", к примеру, прочитает одним блоком то, что "file of T" читал бы как 30 блоков.<br>
<br>
Ну и, конечно, всё то же самое касается и "Write".<br>
<hr />
</div>
</body>
</html>

View file

@ -1,289 +0,0 @@
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;
// 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<T>
BR = record
s: ValueString255;
dt: DateTime; // А BlockFileOf<T> принимает любые размерные типы без ограничений
i: integer;
ch: char;
b: byte;
end;
///Это тип для ввода/вывода
///В нём хранятся входные данные, общие и для file of T, и для BlockFileOf<T>
///Тут же описаны и преобразования всех особых типов
///Заметьте, преобразование string[255] к string и назад ничего не копирует и не преобразовывает, если изначальная строка уже была <= 255 символов
///Поэтому преобразования между ValueString255 и string работают медленнее
///И даже при этом BlockFileOf<T> всё равно быстрее
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<BR>('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<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 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<T>, потому что он сохраняет их всех сразу.
//Но он быстрее даже если сохранять по одному элементу
var t1, t2: int64;
var f1: file of AR;
Assign(f1, 'temp.bin');
var f2 := new BlockFileOf<BR>('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.

View file

@ -1 +0,0 @@

View file

@ -0,0 +1,12 @@
begin
var n := 300000000;
var a := ArrFill(n,1);
var l := Lst(a);
MillisecondsDelta;
for var i:=0 to n-1 do
a[i] := 2;
MillisecondsDelta.Println; // 143 ms
for var i:=0 to n-1 do
l[i] := 2;
MillisecondsDelta.Println; // 357 ms
end.

View file

@ -0,0 +1,25 @@
var ss:int64 := 0;
procedure p(s: string);
begin
s[1] := '1';
end;
procedure q(var s: string);
begin
s[1] := '1';
end;
begin
var n := 100000000;
var s := 'a'*n;
MillisecondsDelta;
loop 10 do
p(s);
Println(s[:3]);
MillisecondsDelta.Println;
loop 10 do
q(s);
Println(s[:3]);
MillisecondsDelta.Println;
end.

View file

@ -1 +1 @@
3.8.2.3059
3.8.2.3061

View file

@ -1 +1 @@
!define VERSION '3.8.2.3059'
!define VERSION '3.8.2.3061'

View file

@ -404,6 +404,15 @@ namespace PascalABCCompiler.SystemLibrary
private static basic_function_node _ulong_shr;
//float
private static basic_function_node _float_to_byte;
private static basic_function_node _float_to_sbyte;
private static basic_function_node _float_to_short;
private static basic_function_node _float_to_ushort;
private static basic_function_node _float_to_int;
private static basic_function_node _float_to_uint;
private static basic_function_node _float_to_long;
private static basic_function_node _float_to_ulong;
private static basic_function_node _float_to_double;
private static basic_function_node _float_assign;
private static basic_function_node _float_unmin;
@ -1801,10 +1810,27 @@ namespace PascalABCCompiler.SystemLibrary
_double_to_int = make_type_conversion(_ulong_type, _integer_type, type_compare.non_comparable_type, SemanticTree.basic_function_type.ultoi);
_double_to_char = make_type_conversion(_ulong_type, _char_type, type_compare.greater_type, SemanticTree.basic_function_type.ultochar);
*/
_double_to_int = make_type_conversion(_double_type, _integer_type, type_compare.greater_type, SemanticTree.basic_function_type.dtoi,false);
_float_to_sbyte = make_type_conversion(_float_type, _sbyte_type, type_compare.greater_type, SemanticTree.basic_function_type.ftosb, false);
_float_to_byte = make_type_conversion(_float_type, _byte_type, type_compare.greater_type, SemanticTree.basic_function_type.ftob, false);
_float_to_ushort = make_type_conversion(_float_type, _ushort_type, type_compare.greater_type, SemanticTree.basic_function_type.ftous, false);
_float_to_short = make_type_conversion(_float_type, _short_type, type_compare.greater_type, SemanticTree.basic_function_type.ftos, false);
_float_to_ulong = make_type_conversion(_float_type, _uint64_type, type_compare.greater_type, SemanticTree.basic_function_type.ftoul, false);
_float_to_long = make_type_conversion(_float_type, _int64_type, type_compare.greater_type, SemanticTree.basic_function_type.ftol, false);
_float_to_uint = make_type_conversion(_float_type, _uint_type, type_compare.greater_type, SemanticTree.basic_function_type.ftoui, false);
_float_to_int = make_type_conversion(_float_type, _integer_type, type_compare.greater_type, SemanticTree.basic_function_type.ftoi, false);
_double_to_sbyte = make_type_conversion(_double_type, _sbyte_type, type_compare.greater_type, SemanticTree.basic_function_type.dtosb, false);
_double_to_byte = make_type_conversion(_double_type, _byte_type, type_compare.greater_type, SemanticTree.basic_function_type.dtob, false);
_double_to_ushort = make_type_conversion(_double_type, _ushort_type, type_compare.greater_type, SemanticTree.basic_function_type.dtous, false);
_double_to_short = make_type_conversion(_double_type, _short_type, type_compare.greater_type, SemanticTree.basic_function_type.dtos, false);
_double_to_ulong = make_type_conversion(_double_type, _uint64_type, type_compare.greater_type, SemanticTree.basic_function_type.dtoul, false);
_double_to_long = make_type_conversion(_double_type, _int64_type, type_compare.greater_type, SemanticTree.basic_function_type.dtol, false);
_double_to_uint = make_type_conversion(_double_type, _uint_type, type_compare.greater_type, SemanticTree.basic_function_type.dtoui, false);
_double_to_int = make_type_conversion(_double_type, _integer_type, type_compare.greater_type, SemanticTree.basic_function_type.dtoi, false);
_double_to_float = make_type_conversion(_double_type, _float_type, type_compare.greater_type, SemanticTree.basic_function_type.dtof);
//Унарные операции.
_real_unmin=make_unary_operator(compiler_string_consts.minus_name, _double_type, SemanticTree.basic_function_type.dunmin);
_real_unmin = make_unary_operator(compiler_string_consts.minus_name, _double_type, SemanticTree.basic_function_type.dunmin);
//make_empty_operator(compiler_string_consts.plus_name, _real_type);
make_unary_empty_operator(compiler_string_consts.plus_name, _double_type, _double_type);
//Арифметические операции.