Mirror

Understanding what files are and choosing a Delphi file type - part 2 (Views: 706)


Problem/Question/Abstract:

What is a File? How are they stored? What format is best for my project? - The second part of a series by Philip Rayment

Answer:

Which file type should I use?

By now I hope you understand that, in theory, you can use any Delphi file type to read and write any file (although using a TextFile to read a file that is not text generally won't work very well). You could, for example, use an untyped file or a file of char, to read and write ASCII text. You could, in theory, use an untyped file to write an executable program file.

In practice, of course, particular file types work better with some files than others. If your file contains just strings of text, TextFile is the obvious choice. Similarly, if your file contains data of a single type (including records), a typed file is the way to go. However, there are always going to be those occasions when you want a file to contain data of various types. For example, a Windows resource file might contain some icons, some bitmaps, and some cursors.

Let us have a look at examples of how to save some employee data with each of the three file types. We will include a file version number at the start of the file. Unless you are sure that your file format will never change, a file version number is a good idea so that your program will know what format the data is in. The Untyped and Text files will also include a value indicating the number of records. This is not essential but assists with reading in the information.

The Typed file does not need this as the number of records can be easily calculated by dividing the size of the file by the size of the type. There is of course more than one way to write and read files; the examples below are not necessarily the only ways. In each of these examples let's assume the following declarations:

type
    PersonRecord  =  packed record
      ChistianName:  string[15];
      Surname:       string[15];
      Address1:      string[30];
      Address2:      string[30];
      Town:          string[15];
      Postcode:      word;
     {zip code for Americans}
      Birthdate:     Tdate;
      YearsService:  byte
      ID:            word;
    End;
      {PersonRecord} {this record is 114 bytes long}

var
  People: array of PersonRecord;

const
     LatestFileVersion  =  3;

We will use two records with the following values so that we can work out the file sizes for each method:


Record 1
Record 2
ChistianName
Fred
Josephine
Surname
Smith
Black-Forest
Address1
13 Railway Crescent
Flat 16
Address2

144 Carrington Highway
Town:
Smallville
Williamstown East
Postcode
9053
8405
Birthdate
29-2-1952
25-12-1970
YearsService
15
3
ID
14587
34423


In the code examples, the figures in square brackets are the bytes written by each statement for each of the two records.

Example 1: Untyped File

procedure WriteFile(filename:string);
var
  fil: file;
  i: integer;
  num: word;     {allows up to 65535 records}
const  ver:    byte = LatestFileVersion;

   procedure WriteString(s:ShortString);
   begin   {WriteString}
     BlockWrite(fil,s,succ(length(s));
   end;   {WriteString}

begin   {WriteFile}
   assignFile(fil,filename); rewrite(fil,1); {Create the file}
   BlockWrite(fil,ver,sizeof(ver));                     [1]{Write the file version}
   num:=length(People);
   BlockWrite(fil,num,sizeof(num));                     [2]{Write the number
                                                                                                                                                                                                                                of records}
   for i:=0 to high(people) do
       with people[i] do begin{write the data}
         WriteString(ChristianName);                    [5,9]
         WriteString(Surname);                          [6,13]
         WriteString(Address1);                         [20,8]
         WriteString(Address2);                         [1,23]
         WriteString(Town);                             [11,18]
         BlockWrite(fil,Postcode,sizeof(Postcode));[2,2]
         BlockWrite(fil,Birthdate,sizeof(Birthdate));[8,8]
         BlockWrite(fil,YearsService,sizeof(YearsService)); [1,1]
         BlockWrite(fil,ID,sizeof(ID));                     [2,2]
       end;   {with}
   CloseFile(fil);
end;   {WriteFile}

procedure ReadFile(filename:string);
var    fil:    file;
       i:      integer;
       num:    word;     {allows up to 65535 records}
                         ver:    byte;

   function ReadString:ShortString;
   begin   {ReadString}
     BlockRead(fil,result,1);               {Read the length of the string}
     BlockRead(fil,s[1],length(s));         {Read the string itself}
   end;   {ReadString}

begin   {ReadFile}
   assignFile(fil,filename); reset(fil,1);  {Open the file}
   BlockRead(fil,ver,sizeof(ver));      {Read the file version}
   BlockRead(fil,num,sizeof(num));      {Read the number of records}
   SetLength(People,num);
   for i:=0 to high(people) do
       with people[i] do begin  {Read the data}
         ChristianName:=ReadString;
         Surname:=ReadString;
         Address1:=ReadString;
         Address2:=ReadString;
         Town:=ReadString;
         BlockRead(fil,Postcode,sizeof(Postcode));
         BlockRead(fil,Birthdate,sizeof(Birthdate));
         BlockRead(fil,YearsService,sizeof(YearsService));
         BlockRead(fil,ID,sizeof(ID));
       end;   {with}
   CloseFile(fil);
end;   {WriteFile}

Analysis

The total file size is 143 bytes, the smallest of our examples, but the most complex to write. We had to use a temporary variable (ver) as the BlockWrite statement requires variables, not constants. If we later need to increase the maximum length of a surname, for example, changing the record declaration is all that is required.

Example 2: File of Record

procedure WriteFile(filename:string);
var
  fil:    file of PersonRecord;
  i:      integer;
  rec:    PersonRecord
begin   {WriteFile}
   assignFile(fil,filename); rewrite(fil); {Create file}
   fillchar(rec,sizeof(rec),0);  {clear fields (not necessary)}
   rec.postcode:=LatestFileVersion;       {any suitable numeric field would do}
   Write(fil,rec);              [114]       {write a record containing the
                                                                                                                                                                        file version}
   for i:=0 to high(people) do
     Write(fil,People[i]);      [114,114]  {Write the data}
   CloseFile(fil);
end;   {WriteFile}

procedure ReadFile(filename:string);
var fil:    file of PersonRecord;
  i: integer;
  rec: PersonRecord
  ver: byte;
begin   {ReadFile}
  assignFile(fil,filename); reset(fil);  {Open the file}
  Read(fil,rec);  {Read a record containing the file version...}
  ver:=rec.postcode;  {... and extract the file version from it}
  SetLength(people,pred(filesize(fil) div sizeof(rec));   {calculate number of records.}
  for i:=0 to high(people) do Read(fil,People[i]);  {Read the data}
  CloseFile(fil);
end;   {ReadFile}

Analysis

The total file size is 342 bytes, by far the largest of our examples, but also the easiest to write. The space is in the unused parts of the strings, which were designed to hold the largest likely names and addresses, plus in the additional record we used at the start just to hold the file version. If we decide later that we need to allow longer strings, we not only need to change the record definition, but also all the files already written this way . Thus while it is the easiest to write, it is probably the hardest to change.

<< Back to main page