With new users purchasing Delphi every single day, it’s not uncommon for me to meet users that are new to the Object Pascal language. One such new user contacted me recently with questions about reading and writing structured data to files on disk.
In actual fact, this customer was quite specific about the file formats of interest.

  1. Flat files of fixed length records with fixed length fields.
  2. Variable length fields / records where the file contains the size of a field, and then it’s data.
  3. Character delimited files such as CSV (comma separated values).

*A warning to advanced readers, this post is not for you.

None of these file formats are all too common anymore. Modern applications tend to use a known standard such as XML or JSON, for which classes are provided with Delphi. I can still see the value in using the older file types however, for the purposes of interoperability with older systems for example. There are also a few lessons to be learnt about file handling which have merit. So lets take a look at a solution to each of these file types.

Flat files of fixed length records.

In order to answer this question, I turned to Delphi Basics::http://www.delphibasics.co.uk/
Delphi basics is an excellent resource for users new to Object Pascal. It functions as a great reference to the fundamental syntax features and available units and classes. While it is not a tutorial website, I would recommend every new user put this bookmark in their browser!

This article http://www.delphibasics.co.uk/Article.asp?Name=Files contains a section entitled “Reading and writing to typed binary files” which contains an example of working with flat files of fixed length records. I modified the sample slightly to run in the command-line:

program structuredbinary;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils; type
TCustomer = record
name : string[20];
age : Integer;
male : Boolean;
end; var
myFile : File of TCustomer; // A file of customer records
customer : TCustomer; // A customer record variable begin
// Try to open the Test.cus binary file for writing to
AssignFile(myFile, 'Test.cus');
ReWrite(myFile); // Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
Write(myFile, customer); customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
Write(myFile, customer); // Close the file
CloseFile(myFile); // Reopen the file in read only mode
FileMode := fmOpenRead;
Reset(myFile); // Display the file contents
while not Eof(myFile) do begin
Read(myFile, customer);
if customer.male then begin
Writeln('Man with name '+customer.name+' is '+IntToStr(customer.age));
end else begin
Writeln('Lady with name '+customer.name+' is '+IntToStr(customer.age));
end;
end; // Close the file for the last time
CloseFile(myFile);
Readln;
end.

In this example you can see that the file ‘myFile’ uses the datatype ‘File of TCustomer’ where ‘TCustomer’ is a record with a fixed number of bytes. The ‘name’ field is twenty characters in length, which in modern Delphi is forty bytes due to the use of UTF-16LE for the string. This is followed by a 32-bit integer for the field ‘age’ and another 32-bits for the boolean field ‘male’ to represent gender.

When using the ‘File of…’ data types, the compiler will assume you are referring to a flat binary file containing nothing but repetitions of the data type which you specify. This is convenient, and particularly useful for records of fixed length which are to be read sequentially.

Files with variable length fields.

The second type of file of interest, is a file with variable length fields. This gives us an opportunity to look at a more modern method of storing data to files, using streams. I took the example from the first file type above, and rewrote it as follows…

program structuredbinarystream;
{$APPTYPE CONSOLE}
{$R *.res}
uses
classes,
System.SysUtils; type
TCustomer = record
name : string;
age : Integer;
male : Boolean;
end; procedure WriteCustomerToStream( customer: TCustomer; FS: TStream );
var
strLength: integer;
idx: integer;
ch: char;
begin
// get the length of the name field.
strLength := Length(customer.name);
// write the length
FS.Write(strLength,sizeof(strLength));
// write the string a character at a time
for idx := 1 to strLength do begin
ch := customer.name[idx];
FS.Write(ch,sizeof(ch));
end;
// write the age and gender
FS.Write(customer.age,sizeof(customer.age));
FS.Write(customer.male,sizeof(customer.male));
end; procedure ReadCustomerFromStream( var customer: TCustomer; FS: TFileStream );
var
strLength: integer;
idx: integer;
ch: char;
begin
// read length of name field.
FS.Read(strLength,sizeof(strLength));
//reading back string a character at a time...
customer.name := '';
for idx := 1 to strLength do begin
FS.Read(ch,sizeof(ch));
customer.name := customer.name + ch;
end;
// reading back age and gender.
FS.Read(customer.age,sizeof(customer.age));
FS.Read(customer.male,sizeof(customer.male));
end; var
FS: TFileStream;
customer : TCustomer; // A customer record variable begin
// Try to open the Test.cus binary file for writing to
FS := TFileStream.Create('Test.cus',fmCreate);
try
// Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
WriteCustomerToStream(customer,FS);
customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
WriteCustomerToStream(customer,FS);
finally
FS.Free;
end; // Reopen the file in read only mode
FS := TFileStream.Create('Test.cus',fmOpenRead);
try
while FS.Position<FS.Size do begin
ReadCustomerFromStream( customer, FS );
if customer.male then begin
Writeln('Man with name '+customer.name+' is '+IntToStr(customer.age));
end else begin
Writeln('Lady with name '+customer.name+' is '+IntToStr(customer.age));
end;
end;
finally
FS.Free;
end; // key to finish
Readln;
end.

In this program I’m using the ‘TFileStream’ class to write to, and then read from the file sequentially.  The ‘TCustomer’ data type now has a variable length string field for ‘name’.  I’ve added two procedures, one for writing a ‘TCustomer’ record to the file, and another to read a ‘TCustomer’ from a file. In each of them, the name field is handled using a loop to read or write one character (two bytes) at a time.

In the WriteCustomerToStream() procedure, I first measure the length of the string (in characters) and write that value to the stream, followed immediately by each individual character. In ReadCustomerFromStream() I am reading the number of characters back from the stream first, and then immediately loading that number of characters from the stream. This is how we allow for the varying length of data for this field.

Using streams to read and write data is a good modern way to handle reading and writing files. Here are some of the reasons why you *should* use streams:

  1. TFileStream is descended from TStream, in my example code above you’ll notice that the procedures WriteCustomerToStream() and ReadCustomerFromStream() take a TStream parameter, not a TFileStream. This allows any descendant of TStream to be used. Instead of writing data to a file, what if you wanted to write it to a database blob field using a TBlobStream class? Well, because those procedures work on the base class TStream, you can simply pass your blob stream class to them. Similarly you might send the data over a network using a network stream class.
  2. The TFileStream class abstracts you from the underlying operating system calls for reading and writing files. This code is therefore portable to other platforms without change (provided the correct implementation of TFileStream is available for that platform).
  3. In the example the TCustomer record could have been a class, and the WriteCustomerToStream() and ReadCustomerFromStream() procedures could have been methods of that class. In fact, renaming these to SaveToStream() and LoadFromStream() respectively, and then adding these methods to a base class, permits for some great structured data nesting options. A similar system is used by the Delphi IDE to save forms to and load forms from files in processes named ‘serialization’ (structured data to stream) and ‘deserialization’ (structured data from stream).

CSV files

Handling CSV files correctly, should be done using streams as in the above example, combined with a simple parser to ensure the CSV format is adhered to. For example, many CSV formats permit commas inside content data under the provision that the content data is surrounded by quotation characters. Some intelligence in the form of a parser is necessary to handle such situations. Having already provided the streaming example above however, parsing the data structure really is another exercise. So for this file I provided the following ‘hack’ method (of course, explaining that it is such)…

program stringlists;
{$APPTYPE CONSOLE}
{$R *.res}
uses
classes,
System.SysUtils; const
CRLF = #13 + #10; //- CR and LF characters, ASCII 13, 10 in decimal
TAB = #09; // TAB character // content of the file...
cFileContent = 'a,b,c' + CRLF +
'1,2,3' + CRLF +
'4,5,6' + CRLF +
'7,8,9' + CRLF; var
FileContent: TStringList;
Fields: TStringList;
idx: longint;
idy: longint; begin
// First we'll save the CSV content from cFileContent into a file 'testfile.csv'
FileContent := TStringList.Create;
try
FileContent.Text := cFileContent;
FileContent.SaveToFile('testfile.csv');
finally
FileContent.Free;
end; // Now load the file back into memory...
FileContent := TStringList.Create;
try
FileContent.LoadFromFile('testfile.csv');
// Now lets parse the field content of each line of the file...
for idx := 0 to pred(FileContent.Count) do begin
Fields := TStringList.Create;
try
Fields.Delimiter := ','; // <--- We're using a comma to delimit fields.
Fields.DelimitedText := FileContent.Strings[idx]; // <-- parse one line of file.
for idy := 0 to pred(Fields.Count) do begin
Write(Fields[idy]);
// if it's not the last field, add a comma..
if idy<pred(Fields.Count) then begin
Write(TAB + ',' + TAB);
end;
end;
Writeln; // new line
finally
Fields.Free;
end;
end; finally
FileContent.Free;
end;
// key to finish
Readln;
end.

This method really isn’t good code!

What I’ve done in this program is to use the properties and methods of the TStringList class to handle saving data to a file, and loading it back. I’ve also used the TStringList class to parse each record using the ‘DelimitedText’ property, which will separate the string by a ‘Delimiter’, in this case a comma. The reason why I call this bad code is that it simply doesn’t take into account the parsing scenarios that I mentioned above. That being said, if you have a very simple CSV format file such as the one used in this sample, this quick-trick method can save you some time doing the parsing yourself.

For beginners to Object Pascal, the above samples should work if you copy and paste the code into a new “Command-Line” project. I didn’t go into every detail, and leave it as an exercise for you to try out, and to study the examples. *hint* Be sure to check out Delphi Basics as a reference! http://www.delphibasics.co.uk/

http://chapmanworld.com/2015/02/19/file-handling-in-delphi-object-pascal/

File handling in Delphi Object Pascal(处理record类型)的更多相关文章

  1. Object Pascal中文手册 经典教程

    Object Pascal 参考手册 (Ver 0.1)ezdelphi@hotmail.com OverviewOverview(概述)Using object pascal(使用 object p ...

  2. Object Pascal 数据类型

     数据类型与定义变量 Object Pascal 语言的最大特点是对数据类型的要求非常严谨.传递给过程或函数的参数值必须与形参的类型一致.在Object Pascal 语言中不会看到像C 语言编译器提 ...

  3. Delphi APP 開發入門(六)Object Pascal 語法初探

    Delphi APP 開發入門(六)Object Pascal 語法初探 分享: Share on facebookShare on twitterShare on google_plusone_sh ...

  4. 终于懂了:Delphi的函数名不是地址,取地址必须遵守Object Pascal的语法(Delphi和C的类比:指针、字符串、函数指针、内存分配等)good

    这点是与C语言不一样的地方,以前我一直都没有明白这一点,所以总是不明白:函数地址再取地址算怎么回事? ------------------------------------------------- ...

  5. Object Pascal 语言基础

    Delphi 是以Object Pascal 语言为基础的可视化开发工具,所以要学好Delphi,首先要掌握的就是Object Pascal 语言.Object Pascal语言是Pascal之父在1 ...

  6. Object Pascal 过程与函数

    过程与函数 过程与函数是实现一定功能的语句块,是程序中的特定功能单元.可以在程序的其他地方被调用,也可以进行递归调用.过程与函数的区别在于过程没有返回值,而函数有返回值. 1.过程与函数的定义 过程与 ...

  7. Object Pascal 运算符

          Object Pascal 的运算符 运算符是程序代码中对各种类型的数据进行计算的符号,通常分为算数运算符.逻辑运算符.比较运算符和按位运算符. 1.算术运算符Object Pascal ...

  8. Object Pascal 语法之异常处理

    http://www.cnblogs.com/spider518/archive/2010/12/30/1921298.html 3 结构化异常处理 结构化异常处理(SHE)是一种处理错误的手段,使得 ...

  9. Object Pascal 面向对象的特性

    2 面向对象的特性 在软件系统开发过程中,结构分析技术和结构设计技术具有很多优点,但同时也存在着许多难以克服的缺点.因为结构分析技术和结构设计技术是围绕着实现处理功能来构造系统的,而在系统维护和软件升 ...

随机推荐

  1. 【a402】十进制数转换为八进制数

    Time Limit: 1 second Memory Limit: 32 MB [问题描述] 用递归算法把任一给定的十进制正整数m(m≤32000)转换成八进制数输出.(要求:同学在做本题时用递归和 ...

  2. (翻译)2016美国数学建模MCM E题(环境)翻译:我们朝向一个干旱的星球?

    PROBLEM E: Are we heading towards a thirsty planet? Will the world run out of clean water? According ...

  3. BZOJ 1260 - 区间dp

    Magic Door 题目大意: 给一个字符串,问需要至少覆盖多少次. 题目分析 区间dp: dp[i][j]表示达到i~j这个状态的最少覆盖次数,分两种情况: s[i] == s[j]: 此时内层可 ...

  4. Apacheserver自己定义404页面的两种方法以及.htaccess的重要命令总结

    Apacheserver自己定义404错误页面有两种方法: 第一种方法最简单,直接在Apache的httpd.conf下进行配置改动命令,改动的内容请參看.htaccess命令写法中的自己定义错误页面 ...

  5. iOS开发 - OC - block的详解 - 基础篇

    深入理解oc中的block 苹果在Mac OS X10.6 和iOS 4之后引入了block语法.这一举动对于许多OC使用者的编码风格改变很大.就我本人而言,感觉block用起来还是很爽的,但一直以来 ...

  6. 【转】cygwin中文乱码(打开gvim中文乱码、安装svn后乱码)

    想用cygwin less看log,可能包含德语.格式是乱的,很多类似"ESC"之类的乱码. 结果这个解决方案似乎也不错,有排版,有颜色高亮. ------------------ ...

  7. 图灵机(Turing Machine)

    图灵机,又称图灵计算.图灵计算机,是由数学家阿兰·麦席森·图灵(1912-1954)提出的一种抽象计算模型,即将人们使用纸笔进行数学运算的过程进行抽象,由一个虚拟的机器替代人们进行数学运算. 所谓的图 ...

  8. Android framework召回(3)binder使用和IBinder BpRefbase IInterface INTERFACE 之间的关系

    status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t de ...

  9. 【转】Mybatis传多个参数(三种解决方案)

    转自: http://www.2cto.com/database/201409/338155.html 据我目前接触到的传多个参数的方案有三种. 第一种方案: DAO层的函数方法 Public Use ...

  10. qt线程(转)----这篇很专业!

    本文档是自己所整理的一份文档,部分是原创,还转贴了网上的一此资料(已经标明了),(难点是多线程的编写),是有源代码的,大家可以作为参考,用到的知识是视频采集,压缩解压(xvid),实时传输(jrtp) ...