Difference between revisions of "File Handling In Pascal"
(Rewrite) |
(Some rewording) |
||
Line 5: | Line 5: | ||
==Old procedural style== | ==Old procedural style== | ||
+ | When using files in classic (non-object oriented) Pascal, you can use a file of type TextFile (or simply Text) to store text, that is typically structured in lines. Every line ends with an end-of-line marker (LineEnding). You can store strings in this type of file but also numbers that are formatted in different ways. These files can be opened and edited inside the Lazarus IDE or any other text editor. | ||
− | + | For specific purposes you can create your own file type that can only store of one type of data. For example: | |
− | |||
<syntaxhighlight>... | <syntaxhighlight>... | ||
type | type | ||
− | TIntegerFile = file of | + | TIntegerFile = file of integer; // Allows you to write only integer numbers to the file |
− | + | TExtendedFile = file of extended; // Allows you to write only real numbers to the file | |
− | + | TCharFile = file of char; // Allows you to write only single characters to the file </syntaxhighlight> | |
− | |||
− | |||
− | |||
− | === | + | ===Input/Output error handling=== |
− | + | The I/O error handling flag tells the compiler how to deal with error situations: raise an exception or store the I/O result to the IOResult variable. | |
− | + | The I/O error handling flag is a compiler directive. To enable or disable it: | |
− | <syntaxhighlight>{$I-} // | + | <syntaxhighlight>{$I-} // Suppress I/O errors: check the IOResult variable for the error code |
− | {$I+} // | + | {$I+} // Errors will lead to an EInOutError exception |
</syntaxhighlight> | </syntaxhighlight> | ||
− | By | + | By suppressing I/O errors ({$I-}) the file operation results go into the IOResult variable. This is a [[Cardinal|cardinal (number) type]]. Different numbers mean different errors. So you may want to check the documentation for the different errors: [http://www.freepascal.org/docs-html/rtl/system/ioresult.html]. |
===File procedures=== | ===File procedures=== |
Revision as of 13:40, 6 March 2015
│
العربية (ar) │
English (en) │
español (es) │
suomi (fi) │
français (fr) │
日本語 (ja) │
русский (ru) │
中文(中国大陆) (zh_CN) │
中文(台灣) (zh_TW) │
Overview
Something every programmer needs to know is how to work with files. Files are used to persist data i.e. store data in such a way that it can be retrieved at a later moment, without having to recreate it. Files can be used to store user settings, error logs, measurement or calculation results, and more. This page explains the basics about file handling.
Old procedural style
When using files in classic (non-object oriented) Pascal, you can use a file of type TextFile (or simply Text) to store text, that is typically structured in lines. Every line ends with an end-of-line marker (LineEnding). You can store strings in this type of file but also numbers that are formatted in different ways. These files can be opened and edited inside the Lazarus IDE or any other text editor.
For specific purposes you can create your own file type that can only store of one type of data. For example:
...
type
TIntegerFile = file of integer; // Allows you to write only integer numbers to the file
TExtendedFile = file of extended; // Allows you to write only real numbers to the file
TCharFile = file of char; // Allows you to write only single characters to the file
Input/Output error handling
The I/O error handling flag tells the compiler how to deal with error situations: raise an exception or store the I/O result to the IOResult variable. The I/O error handling flag is a compiler directive. To enable or disable it:
{$I-} // Suppress I/O errors: check the IOResult variable for the error code
{$I+} // Errors will lead to an EInOutError exception
By suppressing I/O errors ({$I-}) the file operation results go into the IOResult variable. This is a cardinal (number) type. Different numbers mean different errors. So you may want to check the documentation for the different errors: [1].
File procedures
These file handling procedures and functions are located in unit system. See the FPC documentation for more details: Reference for 'System' unit.
- AssignFile (or the older Assign) - Assign a name to a file
- Append - Opens an existing file for appending data to end of file and editing it
- BlockRead - Read data from an untyped file into memory
- BlockWrite - Write data from memory to an untyped file
- CloseFile (or the older Close) - Close opened file
- EOF - Check for end of file
- Erase - Erase file from disk
- FilePos - Get position in file
- FileSize - Get size of file
- Flush - Write file buffers to disk
- IOResult - Return result of last file IO operation
- Read - Read from a text file into variable
- ReadLn - Read from a text file into variable and goto next line
- Reset - Opens a file for reading
- Rewrite - Open file for writing
- Seek - Change position in file
- SeekEOF - Set file position to end of file
- SeekEOLn - Set file position to end of line
- Truncate - Truncate the file at position
- Write - Write variable to a text file
- WriteLn - Write variable to a text file and append newline
Example
A full example of handling a text file of type TextFile:
program FileTest;
{$mode objfpc} // Do not forget this ever
uses
Sysutils;
var
FileVar: TextFile;
begin
WriteLn('File Test');
// Use AssignFile rather than Assign as Assign is used in other units as well
AssignFile(FileVar, 'Test.txt'); // You do not have to put .txt but this is just for now
{$I+} //use exceptions
try
Rewrite(FileVar); // creating the file
Writeln(FileVar,'Hello');
// Use CloseFile rather than Close as Close is used in other units as well
CloseFile(FileVar);
except
on E: EInOutError do
begin
Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
end;
end;
WriteLn('Program finished. Press enter to stop.');
ReadLn;
end.
Now open the file in any text editor and you will see Hello written to it! You can test the error handling by running it once, setting test.txt to read-only and try running it again.
Note that we used exception handling ({$I+}) as that is an easy way to perfom multiple file operations and handling the errors. You could also use {$I-}, but then you would have to check IOResult after each operation and modify your next operation.
Here's how appending/adding to a file works:
program EditFile;
{$mode objfpc}
uses
Sysutils;
var
File1: TextFile;
begin
WriteLn('Append file');
{$I+}
try
AssignFile(File1, 'Test.txt');
Append(File1);
Writeln(File1,'adding some text...');
CloseFile(File1);
except
on E: EInOutError do
begin
Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
end;
end;
WriteLn('Program finished. Press enter to stop.');
Readln;
end.
Reading a file:
program ReadFile;
{$mode objfpc}
uses
Sysutils;
var
File1: TextFile;
Str: String;
begin
Writeln('File Reading:');
AssignFile(File1, 'Test.txt');
{$I+}
try
Reset(File1);
repeat
Readln(File1, Str); // Reads the whole line from the file
Writeln(Str); // Writes the line read
until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
CloseFile(File1);
except
on E: EInOutError do
begin
Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
end;
end;
WriteLn('Program finished. Press enter to stop.');
Readln;
end.
It is possible to do some file handling using chars instead of strings. This makes it look cool :D.
Object style
In addition to the old style file handling routines mentioned above, a new system exists that uses the concept of streams (streams of data) at a higher abstraction level, which means that you as a programmer have to perform fewer steps when dealing with files.
In addition, most string handling classes have the ability to load and save content from/to file. These methods are usually named SaveToFile and LoadFromFile. A lot of other objects (such as Lazarus grids) have similar functionality, including Lazarus datasets (DBExport); it pays to look through the documentation/source code before trying to roll your own import/export routines.
Binary files
For opening files for direct access TFileStream should be used. This class is an encapsulation of the system procedures FileOpen, FileCreate, FileRead, FileWrite, FileSeek and FileClose which resides in unit SysUtils.
In the example below, note how we encapsulate the file handling action with a try..finally block. This makes sure the Filestream object is always released (the finally... part) even if there are file access (or other) errors.
var
Buffer: array[0..1] of Char;
begin
Buffer[0] := 'C';
Buffer[1] := 'B';
with TFileStream.Create('SomeFile.bin', fmCreate) do
try
Write(Buffer[0], SizeOf(Buffer)); //writes CB' to the file
finally
Free;
end;
end;
You can load entire files into memory too if its size is comparatively smaller than available system memory. Bigger sizes might work but your operating system will start using the page/swap file, making the exercise useless from a performance standpoint.
begin
with TMemoryStream.Create do
try
LoadFromFile('SomeFile.bin');
Seek(0, soEnd);
WriteByte(Ord('A')); //Append the byte 65 (character 'A') to the end of the stream
SaveToFile('SomeFile.bin');
finally
Free;
end;
end;
With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.
var
TotalBytesRead, BytesRead : Int64;
Buffer : array [0..4095] of byte; // or, array [0..4095] of char
FileStream : TFileStream;
try
FileStream := TFileStream.Create;
FileStream.Position := 0; // Ensure you are at the start of the file
while TotalBytesRead <= FileStream.Size do // While the amount of data read is less than or equal to the size of the stream do
begin
BytesRead := FileStream.Read(Buffer,sizeof(Buffer)); // Read in 4096 of data
inc(TotalBytesRead, BytesRead); // Increase TotalByteRead by the size of the buffer, i.e. 4096 bytes
// Do something with Buffer data
end;
FileCopy
With the above, we can implement a simple FileCopy function (FreePascal has none in its RTL although Lazarus has copyfile) - adjust as needed for bigger files etc:
program FileCopyDemo;
// Demonstration of FileCopy function
{$mode objfpc}
uses
classes;
const
fSource = 'test.txt';
fTarget = 'test.bak';
function FileCopy(Source, Target: string): boolean;
// Copies source to target; overwrites target.
// Caches entire file content in memory.
// Returns true if succeeded; false if failed
var
MemBuffer: TMemoryStream;
begin
result:=false;
MemBuffer:=TMemoryStream.Create;
try
try
MemBuffer.LoadFromFile(Source);
MemBuffer.Position:=0;
MemBuffer.SaveToFile(Target); //may be same as source
result:=true;
except
result:=false; //swallow exception; convert to error code
end;
finally
MemBuffer.Free;
end;
end;
begin
If FileCopy(fSource, fTarget)
then Writeln('File ', fSource, ' copied to ', ftarget)
else Writeln('File ', fSource, ' not copied to ', ftarget);
Readln();
end.
Text files
In general, for text files you can use the TStringList class to load the entire file into memory and have simple access to their lines. Of course, you can also save your StringList back to file:
program TStringListDemo;
// Stub to demonstrate TStringList class
{$mode objfpc}
uses
Classes;
begin
with TStringList.Create do
try
Add('Hello');
SaveToFile('SomeFile.txt');
finally
Free;
end;
Readln();
end.
In order to write a single string to a stream you might want to use the following procedure:
program SaveStringToPathDemo;
// Stub to demonstrate SaveStringToPath procedure
{$mode objfpc}
uses
classes;
procedure SaveStringToPath(theString, filePath: String);
// Write a single string to a file stream
var
textFile: TFileStream;
begin
textFile := TFileStream.Create(filePath, fmOpenWrite or fmCreate);
try
textFile.WriteBuffer(theString[1], Length(theString));
finally
textFile.Free;
end;
end;
begin
SaveStringToPath('>>', 'SomeFile.txt');
end.