I . Basic File Concepts

 

           

The constructor of File can be overloaded.  One can just open a file with a file name:

 

File inFile = new File(“sample.txt”);

 

In this case the file is assumed to be in the default project directory.  If it is located elsewhere, one can do the following:

 

File inFile = new File(“C:\\Datafiles”,”sample.txt”);

 

In the above case, we are giving it two parameters, the first the directory where it can be found, and the second, the name of the file.  The two “\\” is to indicate the operating system that the ‘D’ is not an escape character.  I.e., we communicate that one has an escape character by having a divide symbol (\) before it.  E.g., to indicate a line feed is to be given, we use “\n”.  However in MS-DOS, file subdirectories are indicated by the same divide symbol.  So to tell the operating system that one wants to go to the subdirectory of the disk C: called Datafiles, we need two of them to indicate that a ‘\’ is not an escape character.

 

Note that we could open the file in one statement as:

 

File inFile = new File(“C:\\Datafiles\sample.txt”);

 

When a valid association is established, we say that the file is opened. We can verify that a file has been correctly opened by:

 

            if (!inFile.exists()

            {

                        //file has not been opened and do error handling

            }

 

 

A file must be opened before performing any file access operation.

 

            Various file manipulations can be done with a file object.  E.g., a file object can be associated with a directory rather than just a particular file.  E.g., to list all of the files in a particular directory, we can do:

 

            File directory = new File(“a:\\Datafiles”);

 

            String filename[] = directory.list();

 

            for(int I = 0; I < filename.length; I++)

                        System.out.println(filename[I];

 

To see whether a file object is associated to file or directory, we have

 

            File file = new File(“a:\\Datafiles”);

 

          if (file.isfile())

                        System.out.println(“I am a file”);

         else

                        System.out.println(“I am a directory”);

 

The link to a partial UML of the File Class discusses in more detail the class File.

 

II. Using the File class and run-time Exceptions

 

          Below is an example where we try to create a new file.  If the file exists, we delete it and create a new file of that name.  If it does not exist, we create it.  We use the try…catch construct to handle an IOExceptions that could be generated.

import java.io.*;
 
public class CreateFile
{
                    public CreateFile()
                    {
                      try
                       {
                              File file;
                              boolean createOK;
                              file = new File("example.txt");
 
                                if (file.exists())
                               {
                                             if (file.isDirectory()
                                                      System.out.println(file.getAbsolutePath() +" is a directory. No file created.");
                                             else
                                            {
                                                      boolean deleteOK = file.delete();
                                                      if (deleteOK)
                                                     {
                                                               System.out.println("Existing file deleted.");
                                                               createOK = file.createNewFile();
                                                               if (createOK)
                                                                           System.out.println("File created.");
                                                               else
                                                                            System.out.println("Unable to create "+
                                                                                    "file "+ file.getAbsolutePath() );
                                                     }
                                                     else
                                                                System.out.println("Unable to delete file " + file.getAbsolutePath() );
                                           }
                             }
                             else   //file does not exits
                             {
                                         createOK = file.createNewFile();
                                         if (createOK)
                                                                 System.out.println("File created");
                                         else
                                                                 System.out.println("Unable to create file " + file.getAbsolutePath() );
 
                            }
 
                    } //end of try clause
                     catch (IOException e)
                                            {System.out.println("I/O error occurred");}
             }
 
}
 

There are many things that can go wrong when a program accesses files or the file system. For example, file systems enforce security restrictions that protect files and directories from unwanted access. These security systems may prohibit file access to certain programs. (E.g., if we replace the 'example.txt' by 'password', which is the file of login names and passwords.)

 

Java incorporates a mechanism, known as exception handling, to manage run-time errors that are detected by the Java Virtual Machine, including those that result from illegal file usage. The term exception refers to a run-time failure. It said that the program throws an exception when it encounters a run-time error that is too severe to continue normally. For example, a file security violation within a program results in an exception. Similarly, when a program attempts to create a new file on a disk that is already full, an exception is thrown.

The preferred way to deal with an exception is a try instruction. A try instruction is a Java statement that has a body, much the same way a method has a body. When the try statement executes, its body executes. If no exceptions are thrown during the execution of the try's body, then the catch clauses are ignored, and execution proceeds as though there were no try instruction.

 

Every Java exception has some type, and if an exception is thrown within a try body and if the exception has a type matching one of the catch clauses, then the remainder of the try body is skipped and the matching catch clause is executed.   The type of exceptions that can occur while using methods from file-related classes all belong to the IOException type. Therefore, in the above code, this exception is used. If an IOException is thrown at any time during the execution of CreateFile method, then the remainder of the method is aborted and the catch clause prints an error message.

 

If the program executes without an exception being thrown, then it first instantiates a File object for a file with the relative path name of example.txt. The outer if  instruction tests to see if this file/directory already exists or not. In the event that exists() returns true, the program proceeds to check whether the existing entity is a file or a directory. If it is a directory, a message is output without attempting to replace this with a file. (Why does it do this?) If, however, it is a file that already exists, then that file is first deleted and a new file created. The boolean value returned by these delete and create operations are assigned, respectively, to the deleteOK and createOK variables. Theses variables are used to print an appropriate message. If the file does not exist in the first place, then a new file is created and an appropriate message is displayed.

 

If an exception is thrown and caught by the appropriate catch clause, then after the catch clause finishes executing, the next instruction to be executed is the first instruction after that clause.  That is to say, if one has:

 

 try

{

a;

}

catch(…)

{

c;
}

b;

 

Suppose during the execution of instruction a there is a run-time exception generated which is caught by the catch phrase.  After the execution of instruction c, then next instruction to be executed is instruction b.  Any instruction after a is inside the try clause is not executed. Instruction a is not completed. 

 

 

III. File I/O  (Streams)

 

To get data into the computer (at the low-level I/O), we would have:

 

File inFile = new File(“Sample.txt”);

FileInputStream inStream = new FileInputStream(inFile);

 

The class FileInputStream is the object, which actually has methods to actually get “streams” of data into the program.  For example,

 

 int fileSize = (int) inFile.length();

 byte [] byteArray = new byte[fileSize};

 

//read data in and display them

inStream.read(byteArray);                         //reads in the entire file

for(int I = 0; I < fileSize; I++)

   System.out.println( (char) byteArray[I]);

 

//close the file as input done

inStream.close();

 

This will read in the entire file and send it to the terminal window.  (This assumes that the file is in ASCII or Unicode or some “human” readable format.)

 

Note that we could just do the creation of the stream by one line:

 

 FileInputStream  inStream = new FileInputStream(“sample.txt”);                                        

 

However, stream objects can be associated to a non-file data source as well.   E.g., a serial port.  The shortcut above does not eliminate the fact that the stream object is associated to a file “sample.txt”.  This is just hidden from you in the constructor.  When a constructor is just sent a String, it is assumed that it is a filename.  The operating system will search for a file of that name in the path associated to Java.

 

Note that FileInputStream’s can not go back once they have read something.  This is what the term Stream means.  The data “streams” forward like a stream.  There is a “pointer” to where in the file one is to read the next byte.  This pointer can not be reversed.  The only way to try and get that which has already been read is to close the file and reopen it at the beginning.

 

However, streams are too primitive for input.  This is because we have to convert them to the proper data types.  To simplify life, there exists a higher level file I/O. These higher level I/O are called DataInputStream and DataOutputStream.  See page 680 and 681 of the text for the diagram.  The code to use them looks like

 

File inFile = new File(“sample.txt”);

 FileInputStream inFileStream = new FileInputStream(inFile);

 DataInputStream inDataStream = new dataInputStream(inFileStream);

 

Its hierarchy is

 

  sample.txt

 

                                                                                              ß bytes are read from file

 

  InFileStream

        ß sequence of bytes are converted to  the primitive data type values

 

 

  InDataStream

 

 ß primitive data  type values are  read from stream

 

 

 readBoolean                 readFloat             readInt                    readDouble                            readChar

 

 

Note that there is no readString, because String is not a primitive type.  The code to get the data is:

 

                int x = inDataStream.readInt();

 

Note that the data being read must be of the format that is expected.  I.e., readInt reads the next 4 bytes, which must be legal representation of integers.  If this is not the case, then an IOException is generated. 

 

               Both FileOutputStream and DataOutputStream objects produce a binary file, in which data is stored in the format (called binary format) in which they are stored as in main memory.  (This means that we can not look directly at the file (via Notepad or Word) and see what is stored there.

 

               Instead of storing data in binary format, we can store the data in ASCII  (in the USA) or Unicode format.  In these formats, all data is converted to String data.  A file whose contents are stored in ASCII (or Unicode) is called a textfile.  One major benefit of a textfile format is that one can easily read and modify the contents of a textfile using  any text editor or word processor.

 

               The object we use to generate a textfile is PrintWriter, while the object to read a textfile is BufferedReader.  PrintWriter only supports two methods, print and println.  We are familiar with these methods because the class out in System is a PrintWriter object.  The code to create a PrintWriter object is:

 

               File outFile = new File(“sample.txt”);

               FileOutputStream outFileStream = new FileOutputStream(outFile);

               PrintWriter outStream = new PrintWriter(outFileStream);

 

In order to read data from a textfile, we have:

 

               File inFile = new File(“sample.txt”);

               FileReader fileReader = new FileReader(inFile);

               BufferedReader bufReader = new BufferedReader(fileReader);

 

This creates the connection.

 

               If we know that the data is stored with 3 data items per line in the file consisting of the “type”, number, price, where “type” is a String, number an int, and price a double:

 

                widget 14 3.35

                spoke 132 0.32

                wrap 58 1.92

 

 Then the following code will read the data into the program:

 

               StringTokenizer tokenizer;

               String line, name, tokenizer;

                int units;

                double price;

 

                line = bufReader.readLine();

                tokenizer = new StringTokenizer(line);

                name = tokenizer.nextToken();

                units = Integer.parseInt(tokenizer.nextToken());

                price = Double.parseDouble(tokenizer.nextToken());

 

will assign the proper values to the proper variables.  The method nextToken will read a String character by character until it finds a white space ( a blank space, a tab space, or a line feed).  This String is then converted via the parse called to the format desired.  Thus, the  first time through the file, the name gets the assigned “widget”, the next token is “14”, which is converted and assigned to unit, and the last (on the line) is “3.35”, which is converted and assigned to price. 

 

               If, however, one has “1A4” instead of “14”, then a NumberFormatException is thrown.  To catch it, so that the program does not terminate, one needs to use the try…catch construct.  I.e., one has

 

               X try

                   {

                               line = bufReader.readLine();

                              tokenizer = new StringTokenizer(line);

                              name = tokenizer.nextToken();

                              units = Integer.parseInt(tokenizer.nextToken());

                              price = Double.parseDouble(tokenizer.nextToken());

                    }

                   catch(NumberFormatException e)

                   {

                                              //handle exception here

                    }

 

An example of this is given in the textbook on page 442 in the method getAge.

 

IV. The Scanner Class

 

            Because of the complications of the levels of hierarchy in the Files class, starting in Java 1.5, a new class was introduced, called the Scanner class.  It is in the class java.util.Scanner, so to use it, one must have

 

import java.util.*;

 

inside the class which is using it.  The Scanner class can only be used for text files (i.e., ASCII encoded files) to parse

primitive data types (char, int, double, etc.).  In general, it will read from the file byte by byte until it finds a whitespace. A whitespace is one of three characters: (1) blank space, (2) tab, or (3) a line feed.  We refer to the data read in between whitespaces as tokens.

 

               The advantage of using an object of the Scanner class is that one only has to create it, and then one can read in data using the appropriate methods of the class.  For example, if we have the data of the type as above:

 

                widget 14 3.35

                spoke 132 0.32

                wrap 58 1.92

 

then the following code will read in it:

 

               String name;

                int units;

                double price;

 

               Scanner scan = new Scanner(new File(“Sample.txt”));

 

               name = scan.next();

               units = scan.nextInt();

               price = scan.nextDouble();

 

 

               The method next, reads in character by character as a char until it finds a whitespace.  Similarly, nextInt reads in character by character (from the previous whitespace) until if finds a whitespace.  (Note that three consecutive characters of whitespace are considered to be just one whitespace.).  nextInt then converts this token into an int.  If the token can not be converted into an int, an InputMismatchException is thrown.  This is similar to the NumberFormatException for the parseInt method of Integer.  So we would modify our code to be

 

               Try

               {

                               name = scan.next();

                               units = scan.nextInt();

                               price = scan.nextDouble();

               }

               catch(InputMisMatchException e)

               {

                               //handle exception here

              }

 

               There are also methods in the Scanner class which allow you to see what is next without having to get it.  E.g., one has a method hasNextInt which returns true if and only if the next token in the scanner’s input can be interpreted as an int.  However, it does not actually get it. 

 

               Note that the Scanner class is like the FileInputStream class in that once one has read some bytes, one can not “back-up”.  To get previous data, one must close the scanner and re-open it at the beginning of the file.

 

 

V. Object I/O

 

            In Java you can store objects just as easily as you store primitive data types.  An object which is retained after program execution is referred to as persistence.

 

               One situation that calls for persistence is a program that keeps track of its final state to use as the initial state the next time the program is executed.  For example, consider a computer program that plays the game of chess.  It might be desirable to write the chess playing program so that the user could suspend the game for a later date.  Such a program is written by making the game board persistent, along with the associated chess pieces.

 

               To write objects to a file, we use the ObjectOutputStream object, and to read objects from a file, we use the ObjectInputStream object.  Let us see how to write Fraction objects to a file.

 

               First, we need to modify the definition of the Fraction class so that ObjectOutputStream and ObjectInputStream can perform object I/O.  We modify the class definition Fraction to (and import the java.io.* where Serializable is defined):

 

  import java.io.*;

 

  public class Fraction implements Serializable

   {

 

//the rest is the same

}

 

To write a Fraction object to a file, we first create the ObjectOutputStream object:

 

 

               File outFile = new File(“sample.txt”);

               FileOutputStream outFileStream = new FileOutputStream(outFile);

               ObjectOutputStream outStream = new ObjectOutputStream(outFileStream);

 

To save a Fraction object, we write

 

               Fraction r1 = new Fraction(1,3);

               outStream.writeObject(r1);

 

               We can save different types of objects to the same file.  Similarly, we can mix primitive data types and objects in the same file:

 

                int n = 5;

               Fraction[] list = new Fraction[5];

              

                outStream.writeInt(n);

                for(int i = 0; i < n; i++)

                               outStream.writeObject(list[i]);

 

There is also a writeDouble, writeChar, etc.  (Remember that String is an object.)

 

               To read objects from a  file,we use FileInputStream and ObjectInputStream.  We use the method readObject to read an object.  Since we can store more than one type of object in the same file, we need to cast the object read from the file:

              

   File inFile = new File(“sample.txt”);

               FileInputStream inFileStream = new FileInputStream(inFile);

               ObjectInputStream inObjectStream = new ObjectInputStream(inFileStream);

 

               Fraction r = (Fraction) inObjectStream.readObject();

 

Because there is a possibility of wrong type casting (the object read is not of type Fraction), the readObject method can throw a ClassNotFoundException, in addition to an IOException. If you propagate both exceptions, then the declaration of a method which contains the call to readObject will look like:

 

                public void myMethod() throws IOException,ClassNotFoundException

 

               To catch the exception, one has

 

                try

                 {

 

Fraction r = (Fraction) inObjectStream.readObject();

 

 

                 }

                 catch(IOException e)

                {

                               //handle exception

                 }

                 catch(ClassNotFoundException e)

                 {

                               //handle exception

                 }

 

Thus, if we mix different types of objects in the same file, we must read them back in the exact order in which we wrote them to the file.

 

               An example of writing and reading an array of Fractions to a file would look like:

 

               Fraction [] list = new Fraction[n];           //assuming there are N elements in the list

 

               //assuming that the array has been created, i.e., list[n] = new Fraction…

 

               //we store the list in a file with the number of elements in the list first

               outObjectStream.writeInt(n);

               for (int i = 0; i < n; i++)

                               outObjectStream.writeObject(list[i]);

 

To read back from the file into an array:

 

                  int n = inObjectStream.readInt();

                  Fraction [] list = new Fraction[n];

 

                   for (int i = 0; i< n; ++i)

                                list[i] = (Fraction) inObjectStream.readObject();

 

Since an array itself is an object, we can actually store the whole array instead of storing the individual elements of the array.

 

                outObjectStream.writeObject(list);

 

The whole array is read back by

 

                 list = (Fraction []) inObjectStream.readObject();

 

Note the type casting of Fraction [].