Standard library: Files and streams¶
Ada provides different approaches for file input/output (I/O):
Text I/O, which supports file I/O in text format, including the display of information on the console.
Sequential I/O, which supports file I/O in binary format written in a sequential fashion for a specific data type.
Direct I/O, which supports file I/O in binary format for a specific data type, but also supporting access to any position of a file.
Stream I/O, which supports I/O of information for multiple data types, including objects of unbounded types, using files in binary format.
This table presents a summary of the features we've just seen:
File I/O option |
Format |
Random access |
Data types |
|---|---|---|---|
Text I/O |
text |
string type |
|
Sequential I/O |
binary |
single type |
|
Direct I/O |
binary |
✓ |
single type |
Stream I/O |
binary |
✓ |
multiple types |
In the following sections, we discuss details about these I/O approaches.
Text I/O¶
In most parts of this course, we used the Put_Line procedure to display
information on the console. However, this procedure also accepts a
File_Type parameter. For example, you can select between standard
output and standard error by setting this parameter explicitly:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Std_Text_Out is
begin
Put_Line (Standard_Output, "Hello World #1");
Put_Line (Standard_Error, "Hello World #2");
end Show_Std_Text_Out;
You can also use this parameter to write information to any text file. To
create a new file for writing, use the Create procedure, which
initializes a File_Type element that you can later pass to Put_Line
(instead of, e.g., Standard_Output). After you finish writing
information, you can close the file by calling the Close procedure.
You use a similar method to read information from a text file. However,
when opening the file, you must specify that it's an input file
(In_File) instead of an output file. Also, instead of calling the
Put_Line procedure, you call the Get_Line function to read
information from the file.
Let's see an example that writes information into a new text file and then reads it back from the same file:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Text_File_IO is
F : File_Type;
File_Name : constant String := "simple.txt";
begin
Create (F, Out_File, File_Name);
Put_Line (F, "Hello World #1");
Put_Line (F, "Hello World #2");
Put_Line (F, "Hello World #3");
Close (F);
Open (F, In_File, File_Name);
while not End_Of_File (F) loop
Put_Line (Get_Line (F));
end loop;
Close (F);
end Show_Simple_Text_File_IO;
---- run info:
In addition to the Create and Close procedures, the standard
library also includes a Reset procedure, which, as the name implies,
resets (erases) all the information from the file. For example:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Text_File_Reset is
F : File_Type;
File_Name : constant String := "simple.txt";
begin
Create (F, Out_File, File_Name);
Put_Line (F, "Hello World #1");
Reset (F);
Put_Line (F, "Hello World #2");
Close (F);
Open (F, In_File, File_Name);
while not End_Of_File (F) loop
Put_Line (Get_Line (F));
end loop;
Close (F);
end Show_Text_File_Reset;
---- run info:
By running this program, we notice that, although we've written the first
string ("Hello World #1") to the file, it has been erased because of the
call to Reset.
In addition to opening a file for reading or writing, you can also open an
existing file and append to it. Do this by calling the Open procedure
with the Append_File option.
When calling the Open procedure, an exception is raised if the
specified file isn't found. Therefore, you should handle exceptions in
that context. The following example deletes a file and then tries to open
the same file for reading:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Text_File_Input_Except is
F : File_Type;
File_Name : constant String := "simple.txt";
begin
-- Open output file and delete it
Create (F, Out_File, File_Name);
Delete (F);
-- Try to open deleted file
Open (F, In_File, File_Name);
Close (F);
exception
when Name_Error =>
Put_Line ("File does not exist");
when others =>
Put_Line
("Error while processing input file");
end Show_Text_File_Input_Except;
---- run info:
In this example, we create the file by calling Create and then
delete it by calling Delete. After the call to Delete, we can
no longer use the File_Type element. After deleting the file, we
try to open the non-existent file, which raises a Name_Error
exception.
Sequential I/O¶
The previous section presented details about text file I/O. Here, we
discuss doing file I/O in binary format. The first package we'll explore is
the Ada.Sequential_IO package. Because this package is a generic
package, you need to instantiate it for the data type you want to use for
file I/O. Once you've done that, you can use the same procedures we've seen
in the previous section: Create, Open, Close, Reset and
Delete. However, instead of calling the Get_Line and Put_Line
procedures, you'd call the Read and Write procedures.
In the following example, we instantiate the Ada.Sequential_IO
package for floating-point types:
with Ada.Text_IO;
with Ada.Sequential_IO;
procedure Show_Seq_Float_IO is
package Float_IO is
new Ada.Sequential_IO (Float);
use Float_IO;
F : Float_IO.File_Type;
File_Name : constant String :=
"float_file.bin";
begin
Create (F, Out_File, File_Name);
Write (F, 1.5);
Write (F, 2.4);
Write (F, 6.7);
Close (F);
declare
Value : Float;
begin
Open (F, In_File, File_Name);
while not End_Of_File (F) loop
Read (F, Value);
Ada.Text_IO.Put_Line
(Float'Image (Value));
end loop;
Close (F);
end;
end Show_Seq_Float_IO;
---- run info:
We use the same approach to read and write complex information. The following example uses a record that includes a Boolean and a floating-point value:
with Ada.Text_IO;
with Ada.Sequential_IO;
procedure Show_Seq_Rec_IO is
type Num_Info is record
Valid : Boolean := False;
Value : Float;
end record;
procedure Put_Line (N : Num_Info) is
begin
if N.Valid then
Ada.Text_IO.Put_Line
("(ok, "
& Float'Image (N.Value) & ")");
else
Ada.Text_IO.Put_Line
("(not ok, -----------)");
end if;
end Put_Line;
package Num_Info_IO is new
Ada.Sequential_IO (Num_Info);
use Num_Info_IO;
F : Num_Info_IO.File_Type;
File_Name : constant String :=
"float_file.bin";
begin
Create (F, Out_File, File_Name);
Write (F, (True, 1.5));
Write (F, (False, 2.4));
Write (F, (True, 6.7));
Close (F);
declare
Value : Num_Info;
begin
Open (F, In_File, File_Name);
while not End_Of_File (F) loop
Read (F, Value);
Put_Line (Value);
end loop;
Close (F);
end;
end Show_Seq_Rec_IO;
---- run info:
As the example shows, we can use the same approach we used for
floating-point types to perform file I/O for this record. Once we
instantiate the Ada.Sequential_IO package for the record type, file
I/O operations are performed the same way.
Direct I/O¶
Direct I/O is available in the Ada.Direct_IO package. This mechanism
is similar to the sequential I/O approach just presented, but allows us to
access any position in the file. The package instantiation and most
operations are very similar to sequential I/O. To rewrite the
Show_Seq_Float_IO application presented in the previous section to use
the Ada.Direct_IO package, we just need to replace the instances of
the Ada.Sequential_IO package by the Ada.Direct_IO
package. This is the new source code:
with Ada.Text_IO;
with Ada.Direct_IO;
procedure Show_Dir_Float_IO is
package Float_IO is new Ada.Direct_IO (Float);
use Float_IO;
F : Float_IO.File_Type;
File_Name : constant String :=
"float_file.bin";
begin
Create (F, Out_File, File_Name);
Write (F, 1.5);
Write (F, 2.4);
Write (F, 6.7);
Close (F);
declare
Value : Float;
begin
Open (F, In_File, File_Name);
while not End_Of_File (F) loop
Read (F, Value);
Ada.Text_IO.Put_Line
(Float'Image (Value));
end loop;
Close (F);
end;
end Show_Dir_Float_IO;
---- run info:
Unlike sequential I/O, direct I/O allows you to access any position in
the file. However, it doesn't offer an option to append information to
a file. Instead, it provides an Inout_File mode allowing reading
and writing to a file via the same File_Type element.
To access any position in the file, call the Set_Index procedure to set
the new position / index. You can use the Index function to retrieve
the current index. Let's see an example:
with Ada.Text_IO;
with Ada.Direct_IO;
procedure Show_Dir_Float_In_Out_File is
package Float_IO is new Ada.Direct_IO (Float);
use Float_IO;
F : Float_IO.File_Type;
File_Name : constant String :=
"float_file.bin";
begin
-- Open file for input / output
Create (F, Inout_File, File_Name);
Write (F, 1.5);
Write (F, 2.4);
Write (F, 6.7);
-- Set index to previous position
-- and overwrite value
Set_Index (F, Index (F) - 1);
Write (F, 7.7);
declare
Value : Float;
begin
-- Set index to start of file
Set_Index (F, 1);
while not End_Of_File (F) loop
Read (F, Value);
Ada.Text_IO.Put_Line
(Float'Image (Value));
end loop;
Close (F);
end;
end Show_Dir_Float_In_Out_File;
---- run info:
By running this example, we see that the file contains 7.7, rather than
the previous 6.7 that we wrote. We overwrote the value by changing the
index to the previous position before doing another write.
In this example we used the Inout_File mode. Using that mode, we just
changed the index back to the initial position before reading from the file
(Set_Index (F, 1)) instead of closing the file and reopening it for
reading.
Stream I/O¶
All the previous approaches for file I/O in binary format (sequential and direct I/O) are specific for a single data type (the one we instantiate them with). You can use these approaches to write objects of a single data type that may be an array or record (potentially with many fields), but if you need to create and process files that include different data types, or any objects of an unbounded type, these approaches are not sufficient. Instead, you should use stream I/O.
Stream I/O shares some similarities with the previous approaches. We still
use the Create, Open and Close procedures. However, instead of
accessing the file directly via a File_Type element, you use a
Stream_Access element. To read and write information, you use the
'Read or 'Write attributes of the data types you're reading
or writing.
Let's look at a version of the Show_Dir_Float_IO procedure from the
previous section that makes use of stream I/O instead of direct I/O:
with Ada.Text_IO;
with Ada.Streams.Stream_IO;
use Ada.Streams.Stream_IO;
procedure Show_Float_Stream is
F : File_Type;
S : Stream_Access;
File_Name : constant String :=
"float_file.bin";
begin
Create (F, Out_File, File_Name);
S := Stream (F);
Float'Write (S, 1.5);
Float'Write (S, 2.4);
Float'Write (S, 6.7);
Close (F);
declare
Value : Float;
begin
Open (F, In_File, File_Name);
S := Stream (F);
while not End_Of_File (F) loop
Float'Read (S, Value);
Ada.Text_IO.Put_Line
(Float'Image (Value));
end loop;
Close (F);
end;
end Show_Float_Stream;
---- run info:
After the call to Create, we retrieve the corresponding
Stream_Access element by calling the Stream function. We then
use this stream to write information to the file via the 'Write
attribute of the Float type. After closing the file and
reopening it for reading, we again retrieve the corresponding
Stream_Access element and processed to read information from the
file via the 'Read attribute of the Float type.
You can use streams to create and process files containing different data
types within the same file. You can also read and write unbounded data
types such as strings. However, when using unbounded data types you must
call the 'Input and 'Output attributes of the unbounded data
type: these attributes write information about bounds or discriminants in
addition to the object's actual data.
The following example shows file I/O that mixes both strings of different lengths and floating-point values:
with Ada.Text_IO;
with Ada.Streams.Stream_IO;
use Ada.Streams.Stream_IO;
procedure Show_String_Stream is
F : File_Type;
S : Stream_Access;
File_Name : constant String :=
"float_file.bin";
procedure Output (S : Stream_Access;
FV : Float;
SV : String) is
begin
String'Output (S, SV);
Float'Output (S, FV);
end Output;
procedure Input_Display (S : Stream_Access) is
SV : String := String'Input (S);
FV : Float := Float'Input (S);
begin
Ada.Text_IO.Put_Line (Float'Image (FV)
& " --- " & SV);
end Input_Display;
begin
Create (F, Out_File, File_Name);
S := Stream (F);
Output (S, 1.5, "Hi!!");
Output (S, 2.4, "Hello world!");
Output (S, 6.7, "Something longer here...");
Close (F);
Open (F, In_File, File_Name);
S := Stream (F);
while not End_Of_File (F) loop
Input_Display (S);
end loop;
Close (F);
end Show_String_Stream;
---- run info:
When you use Stream I/O, no information is written into the file indicating the type of the data that you wrote. If a file contains data from different types, you must reference types in the same order when reading a file as when you wrote it. If not, the information you get will be corrupted. Unfortunately, strong data typing doesn't help you in this case. Writing simple procedures for file I/O (as in the example above) may help ensuring that the file format is consistent.
Like direct I/O, stream I/O support also allows you to access any location in the file. However, when doing so, you need to be extremely careful that the position of the new index is consistent with the data types you're expecting.