7.3 Sequential Files
Sequential files are perfect for three types of persistent data: ASCII text files, "memory dumps", and stream data. Since you're probably familiar with ASCII text files, we'll skip their discussion. The other two methods of writing sequential files deserve more explanation.
A "memory dump" is a file that consists of data you transfer from data structures in memory directly to a file. Although the term "memory dump" suggests that you sequentially transfer data from consecutive memory locations to the file, this isn't necessarily the case. Memory access can, an often does, occur in a random access fashion. However, once the application constructs a record to write to the file, it writes that record in a sequential fashion (i.e., each record is written in order to the file). A "memory dump" is what most applications do when you request that they save the program's current data to a file or read data from a file into application memory. When writing, they gather all the important data from memory and write it to the file in a sequential fashion; when reading (loading) data from a file, they read the data from the file in a sequential fashion and store the data into appropriate memory-based data structures. Generally, when loading or saving file data in this manner, the program opens a file, reads/writes data from/to the file, and then it closes the file. Very little processing takes place during the data transfer and the application does not leave the file open for any length of time beyond what is necessary to read or write the file's data.
Stream data on input is like data coming from a keyboard. The program reads the data at various points in the application where it needs new input to continue. Similarly, stream data on output is like a write to the console device. The application writes data to the file at various points in the program after important computations have taken place and the program wishes to report the results of the calculation. Note that when reading data from a sequential file, once the program reads a particular piece of data, that data is no longer available in future reads (unless, of course, the program closes and reopens the file). When writing data to a sequential file, once data is written, it becomes a permanent part of the output file. When processing this kind of data the program typically opens a file and then continues execution. As program execution continues, the application can read or write data in the file. At some point, typically towards the end of the application's execution, the program closes the file and commits the data to disk.
Although disk drives are generally thought of as random access devices, the truth is that they are only pseudo-random access; in fact, they perform much better when writing data sequentially on the disk surface. Therefore, sequential access files tend to provide the highest performance (for sequential data) since they match the highest performance access mode of the disk drive.
Working with sequential files in HLA is very easy. In fact, you already know most of the functions you need in order to read or write sequential files. All that's left to learn is how to open and close files and perform some simple tests (like "have we reached the end of a file when reading data from the file?").
The file I/O functions are nearly identical to the stdin and stdout functions. Indeed, stdin and stdout are really nothing more than special file I/O functions that read data from the standard input device (a file) or write data to the standard output device (which is also a file). You use the file I/O functions in a manner analogous to stdin and stdout except you use the fileio prefix rather than stdin or stdout. For example, to write a string to an output file, you could use the fileio.puts function almost the same way you use the stdout.puts routine. Similarly, if you wanted to read a string from a file, you would use fileio.gets. The only real difference between these function calls and their stdin and stdout counterparts is that you must supply an extra parameter to tell the function what file to use for the transfer. This is a double word value known as the file handle. You'll see how to initialize this file handle in a moment, but assuming you have a dword variable that holds a file handle value, you can use calls like the following to read and write data to sequential files:fileio.get( inputHandle, i, j, k ); // Reads i, j, k, from file inputHandle. fileio.put( outputHandle, "I = ", i, "J = ", j, " K = ", k, nl );
Although this example only demonstrates the use of get and put, be aware that almost all of the stdin and stdout functions are available as fileio functions, as well (in fact, most of the stdin and stdout functions simply call the appropriate fileio function to do the real work).
There is, of course, the issue of this file handle variable. You're probably wondering what a file handle is and how you tell the fileio routines to work with data in a specific file on your disk. Well, the definition of the file handle object is the easiest to explain - it's just a dword variable that the operating system initializes and uses to keep track of your file. To declare a file handle, you'd just create a dword variable, e.g.,static myFileHandle:dword;
You should never explicitly manipulate the value of a file handle variable. The operating system will initialize this variable for you (via some calls you'll see in a moment) and the OS expects you to leave this value alone as long as you're working with the file the OS associates with that handle. If you're curious, both Linux and Windows store small integer values into the handle variable. Internally, the OS uses this value as an index into an array that contains pertinent information about open files. If you mess with the file handle's value, you will confuse the OS greatly the next time you attempt to access the file. Moral of the story - leave this value alone while the file is open.
Before you can read or write a file you must open that file and associate a filename with it. The HLA Standard Library provides a couple of functions that provide this service: fileio.open and fileio.openNew. The fileio.open function opens an existing file for reading, writing, or both. Generally, you open sequential files for reading or writing, but not both (though there are some special cases where you can open a sequential file for reading and writing). The syntax for the call to this function isfileio.open( "filename", access );
The first parameter is a string value that specifies the filename of the file to open. This can be a string constant, a register that contains the address of a string value, or a string variable. The second parameter is a constant that specifies how you want to open the file. You may use any of the three predefined constants for the second parameter:fileio.r fileio.w fileio.rw
fileio.r obviously specifies that you want to open an existing file in order to read the data from that file; likewise, fileio.w says that you want to open an existing file and overwrite the data in that file. The fileio.rw option lets you open a file for both reading and writing.
The fileio.open routine, if successful, returns a file handle in the EAX register. Generally, you will want to save the return value into a double word variable for use by the other HLA fileio routines (i.e., the MyFileHandle variable in the earlier example).
If the OS cannot open the file, fileio.open will raise an ex.FileOpenFailure exception. This usually means that it could not find the specified file on the disk.
The fileio.open routine requires that the file exist on the disk or it will raise an exception. If you want to create a new file, that might not already exist, the fileio.openNew function will do the job for you. This function uses the following syntax:fileio.openNew( "filename" );
Note that this call has only a single parameter, a string specifying the filename. When you open a file with fileio.openNew, the file is always opened for writing. If a file by the specified filename already exists, then this function will delete the existing file and the new data will be written over the top of the old file (so be careful!).
Like fileio.open, fileio.openNew returns a file handle in the EAX register if it successfully opens the file. You should save this value in a file handle variable. This function raises the ex.FileOpenFailure exception if it cannot open the file.
Once you open a sequential file with fileio.open or fileio.openNew and you save the file handle value away, you can begin reading data from an input file (fileio.r) or writing data to an output file (fileio.w). To do this, you would use functions like fileio.put as noted above.
When the file I/O is complete, you must close the file to commit the file data to the disk. You should always close all files you open as soon as you are through with them so that the program doesn't consume excess system resources. The syntax for fileio.close is very simple, it takes a single parameter, the file handle value returned by fileio.open or fileio.openNew:fileio.close( file_handle );
If there is an error closing the file, fileio.close will raise the ex.FileCloseError exception. Note that Linux and Windows automatically close all open files when an application terminates; however, it is very bad programming style to depend on this feature. If the system crashes (or the user turns off the power) before the application terminates, file data may be lost. So you should always close your files as soon as you are done accessing the data in that file.
The last function of interest to us right now is the fileio.eof function. This function returns true (1) or false (0) in the AL register depending on whether the current file pointer is at the end of the file. Generally you would use this function when reading data from an input file to determine if there is more data to read from the file. You would not normally call this function for output files; it always returns false1. Since the fileio routines will raise an exception if the disk is full, there is no need to waste time checking for end of file (EOF) when writing data to a file. The syntax for fileio.eof isfileio.eof( file_handle );
The following program example demonstrates a complete program that opens and writes a simple text file:program SimpleFileOutput; #include( "stdlib.hhf" ) static outputHandle:dword; begin SimpleFileOutput; fileio.openNew( "myfile.txt" ); mov( eax, outputHandle ); for( mov( 0, ebx ); ebx < 10; inc( ebx )) do fileio.put( outputHandle, (type uns32 ebx ), nl ); endfor; fileio.close( outputHandle ); end SimpleFileOutput; Program 7.1 A Simple File Output Program
The following sample program reads the data that Program 7.1 produces and writes the data to the standard output device:program SimpleFileInput; #include( "stdlib.hhf" ) static inputHandle:dword; u:uns32; begin SimpleFileInput; fileio.open( "myfile.txt", fileio.r ); mov( eax, inputHandle ); for( mov( 0, ebx ); ebx < 10; inc( ebx )) do fileio.get( inputHandle, u ); stdout.put( "ebx=", ebx, " u=", u, nl ); endfor; fileio.close( inputHandle ); end SimpleFileInput; Program 7.2 A Sample File Input Program
There are a couple of interesting functions that you can use when working with sequential files. They are the following:fileio.rewind( fileHandle ); fileio.append( fileHandle );
The fileio.rewind function resets the "file pointer" (the cursor into the file where the next read or write will take place) back to the beginning of the file. This name is a carry-over from the days of files on tape drives when the system would rewind the tape on the tape drive to move the read/write head back to the beginning of the file.
If you've opened a file for reading, then fileio.rewind lets you begin reading the file from the start (i.e., make a second pass over the data). If you've opened the file for writing, then fileio.rewind will cause future writes to overwrite the data you've previously written; you won't normally use this function with files you've opened only for writing. If you've opened the file for reading and writing (using the fileio.rw option) then you can write the data after you've first opened the file and then rewind the file and read the data you've written. The following is a modification to Program 7.2 that reads the data file twice. This program also demonstrates the use of fileio.eof to test for the end of the file (rather than just counting the records).program SimpleFileInput2; #include( "stdlib.hhf" ) static inputHandle:dword; u:uns32; begin SimpleFileInput2; fileio.open( "myfile.txt", fileio.r ); mov( eax, inputHandle ); for( mov( 0, ebx ); ebx < 10; inc( ebx )) do fileio.get( inputHandle, u ); stdout.put( "ebx=", ebx, " u=", u, nl ); endfor; stdout.newln(); // Rewind the file and reread the data from the beginning. // This time, use fileio.eof() to determine when we've // reached the end of the file. fileio.rewind( inputHandle ); while( fileio.eof( inputHandle ) = false ) do // Read and display the next item from the file: fileio.get( inputHandle, u ); stdout.put( "u=", u, nl ); // Note: after we read the last numeric value, there is still // a newline sequence left in the file, if we don't read the // newline sequence after each number then EOF will be false // at the start of the loop and we'll get an EOF exception // when we try to read the next value. Calling fileio.ReadLn // "eats" the newline after each number and solves this problem. fileio.readLn( inputHandle ); endwhile; fileio.close( inputHandle ); end SimpleFileInput2; Program 7.3 Another Sample File Input Program
The fileio.append function moves the file pointer to the end of the file. This function is really only useful for files you've opened for writing (or reading and writing). After executing fileio.append, all data you write to the file will be written after the data that already exists in the file (i.e., you use this call to append data to the end of a file you've opened). The following program demonstrates how to use this program to append data to the file created by Program 7.1:program AppendDemo; #include( "stdlib.hhf" ) static fileHandle:dword; u:uns32; begin AppendDemo; fileio.open( "myfile.txt", fileio.rw ); mov( eax, fileHandle ); fileio.append( eax ); for( mov( 10, ecx ); ecx < 20; inc( ecx )) do fileio.put( fileHandle, (type uns32 ecx), nl ); endfor; // Okay, let's rewind to the beginning of the file and // display all the data from the file, including the // new data we just wrote to it: fileio.rewind( fileHandle ); while( !fileio.eof( fileHandle )) do // Read and display the next item from the file: fileio.get( fileHandle, u ); stdout.put( "u=", u, nl ); fileio.readLn( fileHandle ); endwhile; fileio.close( fileHandle ); end AppendDemo; Program 7.4 Demonstration of the fileio.Append Routine
Another function, similar to fileio.eof, that will prove useful when reading data from a file is the fileio.eoln function. This function returns true if the next character(s) to be read from the file are the end of line sequence (carriage return, linefeed, or the sequence of these two characters under Windows, just a line feed under Linux). This function returns true or false in the EAX register if it detects an end of line sequence. The calling sequence for this function isfileio.eoln( fileHandle );
If fileio.eoln detects an end of line sequence, it will read those characters from the file (so the next read from the file will not read the end of line characters). If fileio.eoln does not detect the end of line sequence, it does not modify the file pointer position. The following sample program demonstrates the use of fileio.eoln in the AppendDemo program, replacing the call to fileio.readLn (since fileio.eoln reads the end of line sequence, there is no need for the call to fileio.readLn):program EolnDemo; #include( "stdlib.hhf" ) static fileHandle:dword; u:uns32; begin EolnDemo; fileio.open( "myfile.txt", fileio.rw ); mov( eax, fileHandle ); fileio.append( eax ); for( mov( 10, ecx ); ecx < 20; inc( ecx )) do fileio.put( fileHandle, (type uns32 ecx), nl ); endfor; // Okay, let's rewind to the beginning of the file and // display all the data from the file, including the // new data we just wrote to it: fileio.rewind( fileHandle ); while( !fileio.eof( fileHandle )) do // Read and display the next item from the file: fileio.get( fileHandle, u ); stdout.put( "u=", u, nl ); if( !fileio.eoln( fileHandle )) then stdout.put( "Hmmm, expected the end of the line", nl ); endif; endwhile; fileio.close( fileHandle ); end EolnDemo; Program 7.5 fileio.eoln Demonstration Program.
1Actually, it will return true under Windows if the disk is full.