Reading and Writing Files


Java, of course, includes mechanisms to open files for reading and writing. In fact, the files can be located on the same machine where the application or applet is running, but can also be located on another machine. 

Java does not read (or write) directly from a file. Instead, all reading writing goes though streams. A Stream is a flowing sequence of characters. Java connects the stream to a file, then reads from the stream which in turn will return the data from the file. Stream classes are defined in the java.io package. The most generic ones are InputStream and OutputStream: 


Class InputStream 

public abstract class java.io.InputStream extends java.lang.Object
{   // Constructors
    public InputStream();
    // Methods
    public void close();
    public void mark(int  readlimit);
    public int read(byte  b[]);
    public void reset(); 
    public long skip(long  n);
}

Class OutputStream 

public abstract class java.io.OutputStream extends java.lang.Object
{   // Constructors
    public OutputStream();
    // Selected Methods
    public void close(); 
    public void flush(); 
    public void write(byte  b[]);
}


However, they are not very useful. For one thing, they do not have any methods to connect the stream to a file, and they can only read bytes. What is needed is a class to handle local files, and/or a class to provide more elaborate methods. Such streams are  FileInputStream and FileOutputStream, and DataInputStream and DataOutputStream. 


Class FileInputStream 

public class java.io.FileInputStream extends java.io.InputStream
{   // Constructors
    public FileInputStream(File  file); 
    public FileInputStream(String  name); 
    // Selected Methods
    public void close();
    protected void finalize();
    public int read();
    public int read(byte  b[]);
    public long skip(long  n);
}

Class FileOutputStream 

public class java.io.FileOutputStream extends java.io.OutputStream
{   // Constructors
    public FileOutputStream(File  file);
    public FileOutputStream(String  name); 
    // Selected Methods
    public void close(); 
    protected void finalize();
    public void write(byte  b[]);
    public void write(int  b); 
}


Both require a File as input to connect the stream to a file. Hence, our last class, before we can start with a few examples, is the File class. 

Class File 

public class java.io.File extends java.lang.Object
{   // Constructors
    public File(File  dir, String  name);
    public File(String  path);
    public File(String  path, String  name);
    // Selected Methods
    public boolean canRead();
    public boolean canWrite();
    public boolean exists();
    public long length();
    public boolean renameTo(File  dest);
}



Now we are ready for some examples. The first one is to simply copy one file to another file. Note that because of our security restrictions, this can only work as a Java program, not an applet. Here's the code: 

import java.io.*;

class FileCopy
{
   public static void main(String[] args) 
   {
      try 
      {
         File fileIn  = new File("source.txt");
         File fileOut = new File("target.txt");

         FileInputStream streamIn   = new FileInputStream(fileIn);
         FileOutputStream streamOut = new FileOutputStream(fileOut);

         int c;
         while ((c = streamIn.read()) != -1) 
         {
            streamOut.write(c);
         }

         streamIn.close();
         streamOut.close();
      }
      catch (FileNotFoundException e) 
      {
         System.err.println("FileCopy: " + e);
      } 
      catch (IOException e) 
      {
         System.err.println("FileCopy: " + e);
      }
   }
}

Note the line for the while loop: a character is read from the stream as an integer. It actually is the integer representation of the character, including any special characters like line feeds, tabs, etc. If the file is empty, the method stores -1 in c. Since -1 is not a valid code for any characters, it indicates the end of the file has been reached. The write method writes the character represented by the integer c to the output stream. 

Now that we are able to read and write data, we also would like more sophisticated data input methods to read the standard data types such as doubles, Strings, etc. For that, the filtered classes DataInputStream and DataOutputStream can be used. 



Class DataInputStream  

public class java.io.DataInputStream extends java.io.FilterInputStream implements java.io.DataInput
{  // Constructors
    public DataInputStream(InputStream  in); 
    // Selected Methods
    public final boolean readBoolean();
    public final char readChar();
    public final double readDouble();
    public final float readFloat();
    public final int readInt();
    public final String readLine();
    public final long readLong();
    public final short readShort();
    public final String readUTF();
}

Class DataOutputStream 

public class java.io.DataOutputStream extends java.io.FilterOutputStream
                                      implements java.io.DataOutput
{   // Constructors
    public DataOutputStream(OutputStream  out);
    // Selected Methods
    public void flush();
    public final void writeBoolean(boolean  v);
    public final void writeChar(int  v);
    public final void writeChars(String  s);
    public final void writeDouble(double  v);
    public final void writeFloat(float  v);
    public final void writeInt(int  v);
    public final void writeLong(long  v);
    public final void writeShort(int  v);
    public final void writeUTF(String  str);
}

  

So, here is a simple example to write an integer, a double, and a string to a file. Again, this must be a standalone program, not an applet. Here is the code: 

import java.io.*;

class WriteData
{
   public static void main(String args[])
   {
      try
      {
         DataOutputStream dataOut = new DataOutputStream(new FileOutputStream("data.txt"));
         dataOut.writeInt(100);
         dataOut.writeChar('\n');
         dataOut.writeDouble(9.8);
         dataOut.writeChar('\n');
         dataOut.writeUTF("Bert Wachsmuth");
         dataOut.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem creating file");
      }
   }
}

Note: The above code will not create a standard ASCII text file. However, the data can be read just fine using the appropriate Java methods for reading. 

To complement this program for writing, let's create a second program to read the data file created by the above program and display the values on the screen. 

import java.io.*;

class ReadData
{
   public static void main(String args[])
   {
      try
      {
         DataInputStream dataIn = new DataInputStream(new FileInputStream("data.txt"));
         int i = dataIn.readInt();
         dataIn.readChar();
         double d = dataIn.readDouble();
         dataIn.readChar();
         String s = dataIn.readUTF();
         dataIn.close();
         System.out.println("Integer: " + i);
         System.out.println("Double:  " + d);
         System.out.println("String:  " + s);
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }
}


Now we can create a meaningfull program. Let's say we need to create a program that reads 10 double numbers from a text file and creates a bar chart representing these numbers. We could go about it as follows: 

import java.io.*;
import java.awt.*;

public class BarChart extends Frame
{
   private int Numbers[] = new int[10];

   public BarChart()
   {
      readData();
      resize(120,120);
      show();
   }

   public void paint(Graphics g)
   {
      for (int i=0; i<10; i++)
         g.drawRect(10*i, 100-Numbers[i], 10, Numbers[i]);
   }

   private void readData()
   {
      try
      {
         DataInputStream dataIn = new DataInputStream(new FileInputStream("data.txt"));
         for (int i=0; i<10; i++)
            Numbers[i] = Integer.parseInt(dataIn.readLine());

        dataIn.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }

   public static void main(String args[])
   {
      BarChart frame = new BarChart();
   }
   public boolean handleEvent(Event e)
   {
      if (e.id == Event.WINDOW_DESTROY)
      {
         dispose();
         System.exit(0);
         return true;
      }
      else
         return false;
   }
}

A couple of things are noteworthy: 

This is a standalone program. Therefore, it must contain the standard main method. To simplify the class, we have created the class to extend Frame, and the main method instantiates an object according to the class containing the main method itself. There's nothing wrong with that, and it provides a quick way to test out class: it can run by itself, without using other classes.
The readData method reads a Line instead of using the readInt method. That is because the data file was created using a text edtitor, and are therefore stored as ASCI text. That, however, is not what the correct representation for Java integers. Hence, we can only use the readInt method if the integers were written by a Java program using the writeInt method. Since that's not the case, we read the integer as a line and convert it to integers after that.

The rest of the program should be pretty straight forward. 

Now suppose we want to read in an unknown amount of integers and place them into a list field. We could modify the above program as follows: 

import java.io.*;
import java.awt.*;

public class ListNumbers extends Frame
{
   private List list = new List(10, false);

   public ListNumbers()
   {
      setLayout(new BorderLayout());
      add("Center", list);
      readData();
      validate();
      pack();
      show();
   }

  public boolean handleEvent(Event e)
   {
      if (e.id == Event.WINDOW_DESTROY)
      {
         dispose();
         System.exit(0);
         return true;
      }
      else
         return false;
   }

   private void readData()
   {
      try
      {
         DataInputStream dataIn = new DataInputStream(new FileInputStream("data.txt"));
         String line;
         while ( (line = dataIn.readLine()) != null)
            list.addItem(line);
         dataIn.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }

   public static void main(String args[])
   {
      ListNumbers frame = new ListNumbers();
   }
}

So, after making the obvious modification in the constructor, we need to modify the readData() method to start a loop that will end when he last line has been read. In this case, we use the fact that the readLine method returns null if the end of file has been reached. Note that the readLine method can not return -1 (since that might be a valid "string"). 

Other common loops use the fact that reading past the end of the file can produce an exception. For example, to read an unknown number of integers using the readInt method, something like the following code snippet will word (the piece different from before is in bold): 

   private void readData()
   {
      try
      {
         DataInputStream dataIn = new DataInputStream(new FileInputStream("data.txt"));
         try
         {
            while ( true )
               list.addItem(String.valueOf(dataIn.readInt()));
         }
         catch(EOFException e)
         {
            System.out.println("End of file");
         }
         dataIn.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }

Here, the readInt method can not return -1 to indicate end of file (since it could be a valid integer read from the file), and it can not return null, since it must return an int (which is not a class so can not be null); thus, another mechanism is used: 

When the readInt (or readDouble and others) encounter reading past the end of file, they will through an EOF exception that exits the - otherwise infinite - loop for us.

Our final code segments will treat reading from a file inside an applet. Recall that our security restictions allow file reading only when the data is located on the same machine where the Java applet is located on. The first approach would be to simply convert the ListNumbers class from above to extend Applet instead of  Frame, and to rename the constructor to init. We also do not need the handleEvent any longer, since an applet can not exit. Here's the code:

import java.io.*;
import java.awt.*;
import java.applet.*;

public class ListNumbersApplet extends Applet
{
   private List list = new List(10, false);

   public void init()
   {
      setLayout(new BorderLayout());
      add("Center", list);
      readData();
   }

   private void readData()
   {
      try
      {
         DataInputStream dataIn = new DataInputStream(new FileInputStream("data.txt"));
         String line;
         while ( (line = dataIn.readLine()) != null)
            list.addItem(line);
         dataIn.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }
}

This, indeed, will work fine as long as the applet is run locally. It will no longer work if the applet is located on a web server and executes on the machine different from the web server. The reason for that is that the FileInputStream class used to connect the stream to the file will generate a security exception when trying to access a file not on the local computer. To be precise, if the applet is not coming from the local computer, the FileInputStream will generate a security exception no matter which file to connect to.

The solution to the dilema is easy. After all, the FileInputStream is only used to connect a stream to a file. Any other class that can accomplish the same would be fine. In fact, our well-known URL class is perfect for that. Recall:

public final class java.net.URL extends java.lang.Object
{   // Constructors
    public URL(String  spec);
    public URL(String  protocol, String  host, int  port, String  file);
    public URL(URL  context, String  spec);
        // Selected Methods
    public final InputStream openStream(); 

          Opens a connection to this URL and return a stream for reading from that connection. 
          This method is a shorthand for 

              openConnection().getInputStream() 
              Returns: 
                    a stream for reading from the URL connection. 
              Throws 
                    IOException if an I/O exception occurs. 
}

  

Therefore, the new code will only change two lines in one method from the previous ListNumbersApplet class: 

import java.io.*;
import java.awt.*;
import java.applet.*;
import java.net.*;

public class ListNumbersApplet extends Applet
{
   private List list = new List(10, false);

   public void init()
   {
      setLayout(new BorderLayout());
      add("Center", list);
      readData();
   }

   private void readData()
   {
      try
      {    
          URL url = new URL(getCodeBase(),"data.txt");
          DataInputStream dataIn = new DataInputStream(url.openStream());
          String line;
          while ( (line = dataIn.readLine()) != null)
             list.addItem(line);
          dataIn.close();
      }
      catch(IOException e)
      {
         System.out.println("Problem finding file");
      }
   }
}

(bgw)