Working with Files

Numerical values are typically stored internally in a binary (base 2) representation. While easy for the computer to read, it is not easy for humans to read. We are accustomed to seeing 101 and thinking that it is one more than one hundred, not 5. In many circumstances, therefore, one reads numerical values written in decimal form (e.g., 3.14159) into a program, which must convert the value to a binary internal representation. As the program runs, various numbers should be output. If these are for direct human consumption, or for export to other unspecified programs, the values are typically displayed in decimal. So, the binary values are converted.

There are several reasons why this is silly in many cases. First, the decimal representation almost always takes more space (bytes). Second, the base conversion procedure is computationally expensive. Third, the decimal representation takes a variable number of bytes. Therefore, you cannot simply jump to the hundredth value in the file with a simple computation; you must scan all the way from the beginning. Therefore, programs that generate significant quantities of numerical data invariably store the data in binary form, usually with a header block to identify various aspects of the data, including textual comments, descriptions of the number and kind of values in the file, and other descriptive information.

You will work with both binary and text data files in this course and beyond. This page has some quick hints about how to manage the basic tasks in C, C++, and Java.


CC++Java

C

In C files are described by a structure called FILE. When you open them, you must specify whether they are for binary operations or ascii, for reading, writing, or appending. There are three basic kinds of input/output operations: formatted i/o, unformatted i/o, and repositioning of the file pointer.

For all examples, you must include the header file stdio.h using the statement

#include <stdio.h>
See that system header file, or a C language reference, for further information on nthese and other stdio commands.

Opening a file

FILE *f;

if ( ! (f = fopen( "myfile", "rb" ) )	)		/* open in binary mode */
{
	printf( "Couldn't open file %s", filename );
	return 2;
}
This snippet opens the file "myfile" (in the current directory or you can specify a complete pathname) for reading in binary mode. Omit the "b" to open the file in text (ascii) mode, which is the default. Use "a" to append to the file, and "w" to write beginning at the start of the file.

Reading from a file

char str[100];
short x;
long y;
char c;

/* binary input */

fread( str, 1, 100, f );        /* read 100 bytes into the string from file f */
fread( (char *) &x, 2, 1, f );  /* read a two-byte short integer;
								   note use of & for address of variable x    */
fread( (char *) &y, 4, 1, f );  /* ditto for a 4-byte integer */

/* text input from file g */

fscanf( g, "%d", &x );          /* read a decimal integer from a text file */
								/* WARNING: failing to pass the pointer to a
								   variable will cause a DISASTER */
fscanf( g, "%s", str );         /* read a line of text into str; DISASTER if
								   the line is longer than 99 bytes */
x = fgetc( g );                 /* returns an int for the char, or EOF */
fgets( str, 99, g );            /* the safe way to read 99 chars; 
								   reading stops at 'n', EOF, or 99 chars */

File positioning

fseek( inf, 120L, SEEK_CUR );   /* move the pointer forward 120 bytes from
								   its current position */
fseek( inf, 100L, SEEK_SET );   /* move the pointer to 100 bytes from the
								   beginning of the file */
fseek( inf, 0L, SEEK_END );     /* move the pointer to the end of the file */

Output to a file

Again, you use different calls to format the output to a text file or to write values to a binary file.
/* text output */

double vals[10];                 /* some data in double precision */
fprintf( g, "x = %d\ty = %g\n", 32, 2.14 );   /* Note that you can specify
									more than one value in a single call */
fwrite( vals, sizeof( double ), 10, f );  /* write binary values to a file */

C++

The philosophy in C++ is to avoid dangerous functions such as the scanf and printf families of functions that take variable arguments and pointers to variables (in the case of scanf). Instead, you typically use the operators >> and << to read a decimal value from a file and write a decimal value to a file, respectively.

In source files that use file streams, include the appropriate header files:

 #include <iostream>              /* might be <iostream.h> */
 #include <fstream>               /* might be <fstream.h>  */
Because C++ is evolving, there is some evolution in the naming scheme of headers. The listed names work in Metrowerks Code Warrior 6, which is the compiler I use. Refer to these header files or the documentation accompanying your compiler for further information.

Text files

The following code fragment opens an existing text file (in the default directory, which is the directory in which the executable program resides) and copies some information from the file into a new file in the same directory.
ifstream inFile( "myInputFile" );
if ( !inFile.is_open() )
{
	cout << "I can't open 'myInputFile'";
	return 2;
}

ofstream outFile( "myGarbage" );  // open the output file
char buffer[256];                 // a string variable

inFile >> buffer;                 // very dangerous, I think, because
								  // input operator doesn't know how many
								  // bytes it is safe to read. Reads a word,
								  // stopping at white space.
inFile.get( buffer, 256 );        // read up to 256 bytes from inFile,
								  // stopping at a newline
fread( buffer, 1, 256, inFile );  // read 256 bytes from inFile

int x;
double y;

inFile << x << y;                 // read integer x and double y, separated by 
								  // white space
outFile << "x is " << x << " and y is "
   << setprecision( 5 ) << y << endl;  // display x and y, ending the line
								  // use 5 decimal places for y
outFile.close();                  // close output file
inFile.close();                   // close input file

Binary files

ifstream inFile( "junk",
  ios::in | ios::binary );        // open the input file in binary mode
if ( !inFile.is_open() )
{
	cout << "I can't open 'junk'";
	return 2;
}

int x;
float y;
double z;

fread( (char *) &x, sizeof( x ), 1, inFile ); // read an int
fread( (char *) &y, sizeof( y ), 1, inFile ); // read a float (4-byte)
fread( (char *) &z, sizeof( z ), 1, inFile ); // read a double

ofstream oFile( "crud" );          // don't forget to check that it opened
if ( !inFile.is_open() )
{
	cout << "I can't open 'crud'";
	return 2;
}

oFile.write( (char *) &z, sizeof( z ) ); // write a double value (in binary)
oFile.close();
inFile.close();

Stream positioning

f.seekg( 100, ios::beg );          // set pointer to 100 bytes from beginning
								   // also ios::end or ios::cur
f.clear();                         // reset an input stream
f.seekg(0, ios::beg);              // and go back to the beginning

Java

Java has all the expected classes for manipulating file streams, but for security purposes access to these streams can be restricted in certain environments. For example, an applet downloaded from an external server is generally granted permission neither to read from the local file system nor to create files. Depending on the environment in which you work, you may or may not be allowed to access the file system. On a Macintosh, applets are typically not granted file system access, but stand-alone Java applications are.

The following program shows how to open files for input and output in either binary or text mode. Note that Java requires that you catch exceptions.

import java.text.*;
import java.io.*;

public class Testing
{
  static FileInputStream inFile;
  static FileOutputStream outFile;
  static PrintWriter outData;
  static DataInputStream inData;
  static NumberFormat nForm = NumberFormat.getInstance();
  
  public static void main( String args[] )
  {
	int x;
	
	File f = new File( System.getProperty( "user.dir" ), "myFile.bin" );
	try
	{
	  inFile = new FileInputStream( f );          // open the input binary file
	  inData = new DataInputStream( inFile );     // associate an input stream to the file
	  
	  f = new File( System.getProperty( "user.dir" ),
		  "outFile.txt" );                        // find path to output file
	  outFile = new FileOutputStream( f );        // ( f, true ) would open for append
	  outData = new PrintWriter( outFile, true ); // associate an output formatter
	}
	catch ( IOException e )
	{
	  System.out.println( "Couldn't open the files: " + e );
	}

  try
  {
	x = inData.readShort();                       // read a 2-byte number
	x += inData.readInt();                        // add a 4-byte number
	inData.skipBytes( 6 );                        // skip over 6 bytes
	x -= inData.readLong();                       // subtract an 8-byte number
	inData.close();                               // close the input file

	outData.print( "I predict your age is " + x );
	outData.println( " years." );
	outData.print( "What do you think of that?" );
	outData.flush();                              // make sure characters are written
	outData.close();                              // close the output file
	}
	catch ( IOException e )
	{
	  System.out.println( "i/o error: " + e );
	}
	System.out.println( "All finished" );
  }
}