Forum > Beginners

Record Question

(1/1)

nugax:
So being a newbie, I just learned this. And have a couple questions about it. Dont have any code on it yet so all this is just examples

Lets say I have:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---typemyrec = recordvar1 var2 end  
and then i create a:

recFile : file of myrec;

and i write the file etc.

How would you guys go about handling an ARRAY of myrec that is saved as a file? Then retrieve multiple records into an array from disk.

I didnt even know you could write files of a rec format. That opens a lot of doors for me!

Objector:
Hello, nugax. Here is a piece of code to create an array of records, write the records to a file, and then read the records from the file. It's simple, but I hope it helps.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program myrecords(input, output, f); {$mode iso} const maxrecords = 100; type myrec = record                intvar: integer;                charvar: char             end ;     recarray = array [1..maxrecords] of myrec;     recfile = file of myrec; var r: myrec;    f: recfile;    i: integer;    a: recarray; procedure setrecord(var r: myrec; i: integer; ch: char);begin   r.intvar := i;   r.charvar := chend {setrecord}; procedure writerecords(var f: recfile; var a: recarray; n: integer);   var i: integer;begin   rewrite(f);   for i := 1 to n do      write(f, a[i])end {writerecords}; procedure readrecords(var f: recfile; var a: recarray);   var i: integer;begin   reset(f);   i := 1;   while not eof(f) do      begin         read(f, a[i]);         i := i + 1      endend {readrecords}; begin   for i := 1 to maxrecords do      begin         setrecord(r, i, chr(i));         a[i] := r      end ;   assign(f, 'rf');   writerecords(f, a, maxrecords);   writeln;   writeln('Contents of file of records:');   readrecords(f, a);   for i := 1 to maxrecords do      writeln(a[i].intvar, a[i].charvar:10);   writelnend .

Handoko:
In the link below have plenty of short demos suitable for beginners, in the File handling category, you can find the Binary file demo showing how to add, delete and show data from binary file:
https://wiki.freepascal.org/Portal:HowTo_Demos

Also you should know about this:
https://wiki.lazarus.freepascal.org/Packed

Warfley:
As an advice, you should not use the file-of record mechanism for a few reasons:
First, it is very inflexible, as it only allows you to write this record to the file. And it does not really give you much of an advantage. Take the following code:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TMyRec = record     ...  end; procedure WriteToFile(AData: TMyRec);var  fl: File of TMyRec;begin  AssignFile(fl, path);  try    Rewrite(fl);    Write(fl, AData);  finally    CloseFile(fl);  end;end; function ReadFromFile: TMyRec;var  fl: File of TMyRec;begin  AssignFile(fl, path);  try    Reset(fl);    Read(fl, Result);  finally    CloseFile(fl);  end;end;So far so easy, but you could also use a filestream to do the same:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TMyRec = record     ...  end; procedure WriteToFile(AData: TMyRec);var  fs: TFileStream;begin  fs := TFileStream.Create(path, fmCreate);  try    fs.Write(AData, SizeOf(AData)  finally    fs.Free;  end;end; function ReadFromFile: TMyRec;var  fs: TFileStream;begin  fs := TFileStream.Create(path, fmOpenRead);  try    fs.Read(Result, SizeOf(Result));  finally    fs.Free;  end;end;So codewise it is not more effort to use a filestream (it is even one line shorter). But now consider you want to write 2 datasets of different types to the file, like two records:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TMyRec = record     ...  end;  TMyOtherRec = record     ...  end; procedure WriteToFile(AData1: TMyRec; AData2: TMyOtherRec);var  fl1: File of TMyRec;  fl2: File of TMyRec;begin  AssignFile(fl1, path);  try    Rewrite(fl1);    Write(fl1, AData1);  finally    CloseFile(fl1);  end;  AssignFile(fl2, path);  try    Append(fl2);    Write(fl2, AData2);  finally    CloseFile(fl);  end;end; procedure ReadFromFile(out AData1: TMyRec; out AData2: TMyOtherRec);var  fl1: File of TMyRec;  fl2: File of TMyRec;begin  AssignFile(fl1, path);  try    Reset(fl1);    Read(fl1, AData);  finally    CloseFile(fl1);  end;  AssignFile(fl2, path);  try    Reset(fl2);    Seek(fl2, SizeOf(AData1);    Read(fl2, AData2);  finally    CloseFile(fl);  end;end;With filestreams:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TMyRec = record     ...  end;  TMyOtherRec = record     ...  end; procedure WriteToFile(AData1: TMyRec; AData2: TMyOtherRec);var  fs: TFileStream;begin  fs := TFileStream.Create(path, fmCreate);  try    fs.Write(AData1, SizeOf(AData1);    fs.Write(AData2, SizeOf(AData2);  finally    fs.Free;  end;end; procedure ReadFromFile(out AData1: TMyRec; out AData2: TMyOtherRec);var  fs: TFileStream;begin  fs := TFileStream.Create(path, fmOpenRead);  try    fs.Read(AData1, SizeOf(AData1));    fs.Read(AData2, SizeOf(AData2));  finally    fs.Free;  end;end;So when you want to read more than one kind of data, typed files are basically unusable, and if you are reading only one type of data, well there is no real advantage over filestreams anyway.

The second point is maintainability. If you want to write an application that receives updates (which in the internet age is pretty much every application except in some very niche domains), you will probably get to the point where the dataformat changes. At this point your typed files are going to be a real pain. Because first, there is no general way to find out which kind of record such a structure uses, as binary data can be interpreted to any form. The closest you can get is checking the size of the file, if it is a multiple of the record size, but even then, if you switch an Integer for a Single, it will have the same size, but the data you read will be complete garbage.

So you need some indication, the usual way to do this is to have a header in the file, where you write parsing information, e.g. a version number of your application, so you can map it to a data format and some form of magic number for integrety checks. Now you first run into the problem with writing different data to the same file, which as described above is quite a pain with typed files. Then when you deploy your update, you need to have two types of records in your code, one for reading old files, and one for reading and writing the new files. WIth the example from above where you change one integer to be a single (for brevety, simply assume that we simply know through magic if its an old file or a new file):

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TMyOldRecord = record  ...  DataField: Integer;end; TMyNewRecord = record  ...  DataField: Single;end; function ReadAndConvertOldData: TMyNewRecord;begin  ...end; function ReadNewData: TMyNewRecord;begin  ...end;So you need to have two records in your code. After the next updates it's 3, then it is 4, 5 and after some update cycles, your whole code will be mostly just defining old records.
Another problem you have with this is, during development, you are going to encounter a lot of bugs, and being able to simply change the contents of a file is often required for debugging purposes. And here binary files are going to make your life really hard. Having to write a special additional program for manually changing binary files is a massive waste of time.
This is also not just during development the case. I like many old games. A lot of old games are easiely capable of being run on large resolutions, but don't provide these options in the menu. But often you can simply open the textual config file, set the resultion by yourself, and it works fine. If this would have been a binary file whose format was lost to history, or never public in the first place, I could not use this software anymore.
Also sometimes you will encounter bugs that write bad data into your configs, if your format is some obscure binary format, the enduser might only have the solution to delete the config file with all the custom config, while with text representation, they can simply fix it themselves.

This all can be easielly avoided by using string serialization, such as json. If you have a new version, all you need to change for backwards compatibility is the serialization procedure. If you have to change some values manually, just open the file in notepad and change the value.
Honestly, unless you are on extremely limited hardware where every bit counts (or in networking), there is absolutely no reason to use binary data formats. It might seem easier in the short term (as you have one read/write command to write whole records), but if you want to build future proof, maintainable and robust software it is much much harder in the long term

BobDog:
I did an example yesterday
https://forum.lazarus.freepascal.org/index.php/topic,58748.msg437869.html#msg437869
Here is a simpler example, but similar.

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  {$RANGECHECKS ON}  uses sysutils;// for deletefile type    MyRecord  = Record    Year      : integer;    Month     : integer;    Day       : integer;    Hour      : integer;    Password  : string[8];    Filler    : Array[1..4] of Char;    messages  : string[40];  End;    type ArrayOfRecord=array of MyRecord;       function filelength(filename:ansistring):longword;Var F : File of byte;var L:longword;beginAssign (F,filename);  Reset (F);  L:=FileSize(F);  Close (F);  exit(L);end; procedure loadfile(var content:ArrayOfRecord;filename:ansistring);   Var Fin : File;   Var x:int32;   begin   x:=filelength(filename) div sizeof(content[0]);   setlength(content,x);   Assign (Fin,filename);   Reset (Fin,x*sizeof(content[0]));   BlockRead (Fin,content[0],1);   close(fin);end;   procedure savefile(content:ArrayOfRecord ;filename:ansistring);    var    fout:file;    begin    Assign(fout,filename);    Rewrite(fout,length(content)*sizeof(content[0]));    blockwrite(fout,content[0],1);    close(fout);  end;   var  z:ArrayOfRecord=nil;  filename:ansistring='array.bin';  ans:ArrayOfRecord=nil;  t,j:integer;    begin  setlength(z,5000);  //set some values  z[0].year:=2022;  z[0].month:=3;  z[0].password:='abcd1234';  z[1].year:=2023;  z[1].month:=4;  z[1].password:='abcd4321';  z[2].year:=2023;  z[2].month:=5;  z[2].password:='dcba1234';  z[high(z)].year:=2024;  z[high(z)].month:=6;  z[high(z)].messages:='Press return to end . . .';    savefile(z,filename);  // part two, reloading file into new array//  loadfile(ans,filename);      writeln((ans[0].year=2022),' year check');   writeln((ans[0].month=3),' month check');   writeln((ans[0].password='abcd1234'),' password check');   writeln((ans[1].year=2023),' year check');   writeln((ans[1].month=4),' month check');   writeln((ans[1].password='abcd4321'),' password check');   writeln((ans[2].year=2023),' year check');   writeln((ans[2].month=5),' month check');   writeln((ans[2].password='dcba1234'),' password check');   writeln((ans[high(ans)].year=2024),' year check');   writeln((ans[high(ans)].month=6),' month check');   writeln((ans[high(ans)].messages='Press return to end . . .'),' messagescheck');      //overall check original file and loaded file  for t:=0 to high(ans) do  begin  if ans[t].year<>z[t].year then writeln('Error');  if ans[t].month<>z[t].month then writeln('Error');  if ans[t].day<>z[t].day then writeln('Error');  if ans[t].hour<>z[t].hour then writeln('Error');  if ans[t].password<>z[t].password then writeln('Error');  for j:=1 to 4 do if ans[t].filler[j]<>z[t].filler[j] then writeln('Error');  if ans[t].messages<>z[t].messages then writeln('Error');  end;     writeln('File size ',filelength(filename),' bytes');  DeleteFile(filename);  writeln; writeln('Does file still exist on disk? ',fileexists(filename)); writeln( '"',ans[high(z)].messages,'"'); readln;    end. 

Navigation

[0] Message Index

Go to full version