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)