Java 1.1 Unleashed
- 12 -
The I/O Package
by Michael Morrison
IN THIS CHAPTER
- Input Stream and Reader Classes
- Output Stream and Writer Classes
- File Classes
It would be impossible for a program to do anything useful without performing
some kind of input or output of data. Most programs require input from the user;
in return, they output information to the screen, printer, and often to files. The
Java I/O package provides an extensive set of classes that handle input and output
to and from many different devices. In this chapter, you learn about the primary
classes contained in the I/O package, along with some examples that show off the
capabilities of these classes.
The I/O package, also known as java.io, contains many classes, each with
a variety of member variables and methods. This chapter does not take an exhaustive
look at every class and method contained in the I/O package. Instead, you can view
this chapter as a tutorial on how to perform basic input and output using the more
popular I/O classes. Armed with the information from this chapter, you will be ready
to begin using the Java I/O classes in your own programs. And should you choose to
explore the more complex I/O classes supported by Java, you will be prepared for
the challenge.
Input Stream and Reader Classes
The Java input model is based on the concept of an input stream. An input stream
can be thought of much like a physical (and certainly more literal) stream of water
flowing from a water plant into the pipes of a water system. The obvious difference
is that an input stream deals with binary computer data rather than physical water.
The comparison is relevant, however, because the data going into an input stream
flows like the water being pumped into a pipe. Data pumped into an input stream can
be directed in many different ways, much as water is directed through the complex
system of pipes that make up a water system. The data in an input stream is transmitted
one byte at a time, which is roughly analogous to individual drops of water flowing
into a pipe.
More practically speaking, Java uses input streams as the means of reading data
from an input source, such as the keyboard. The basic input stream classes supported
by Java follow:
- InputStream
- BufferedInputStream
- DataInputStream
- FileInputStream
- StringBufferInputStream
Java version 1.1 introduces support for character input streams, which are virtually
identical to the input streams just listed except that they operate on characters
rather than on bytes. The character input stream classes are called readers instead
of input streams. Corresponding reader classes implement methods similar to the input
stream classes just listed except for the DataInputStream class. The purpose
of providing character-based versions of the input stream classes is to help facilitate
the internationalization of character information. To find out more about the support
for internationalization in Java, check out Chapter 52, "Java Internationalization."
Following are the basic reader classes provided by Java:
- Reader
- BufferedReader
- FileReader
- StringReader
Throughout this chapter, you use both input streams and reader classes. Because
their supported methods are very similar, it's pretty easy to use one once you've
learned about the other.
The InputStream Class
The InputStream class is an abstract class that serves as the base class
for all other input stream classes. InputStream defines a basic interface
for reading streamed bytes of information. The methods defined by the InputStream
class will become very familiar to you because they serve a similar purpose in every
InputStream-derived class. This design approach enables you to learn the
protocol for managing input streams once and then apply it to different devices using
an InputStream-derived class.
The typical scenario when using an input stream is to create an InputStream-derived
object and then tell it you want to input information by calling an appropriate method.
If no input information is currently available, the InputStream uses a technique
known as blocking to wait until input data becomes available. An example of when
blocking takes place is the case of using an input stream to read information from
the keyboard. Until the user types information and presses Return or Enter, there
is no input available to the InputStream object. The InputStream
object then waits (blocks) until the user presses Return or Enter, at which time
the input data becomes available and the InputStream object can process
it as input.
The InputStream class defines the following methods:
- abstract int read()
- int read(byte b[])
- int read(byte b[], int off, int len)
- long skip(long n)
- int available()
- synchronized void mark(int readlimit)
- synchronized void reset()
- boolean markSupported()
- void close()
InputStream defines three different read() methods for reading
input data in various ways. The first read() method takes no parameters
and simply reads a byte of data from the input stream and returns it as an integer.
This version of read() returns -1 if the end of the input stream
is reached. Because this version of read() returns a byte of input as an
int, you must cast it to a char if you are reading characters.
The second version of read() takes an array of bytes as its only parameter,
enabling you to read multiple bytes of data at once. The data that is read is stored
in this array. You have to make sure that the byte array passed into read()
is large enough to hold the information being read or an IOException is
thrown. This version of read() returns the actual number of bytes read,
or -1 if the end of the stream is reached. The last version of read()
takes a byte array, an integer offset, and an integer length as parameters. This
version of read() is very similar to the second version except that it enables
you to specify where in the byte array you want to place the information that is
read. The off parameter specifies the offset into the byte array to start placing
read data, and the len parameter specifies the maximum number of bytes to read.
The skip() method is used to skip over bytes of data in the input stream.
skip() takes a long value n as its only parameter, which specifies
how many bytes of input to skip. It returns the actual number of bytes skipped or
-1 if the end of the input stream is reached.
The available() method is used to determine the number of bytes of input
data that can be read without blocking. available() takes no parameters
and returns the number of available bytes. This method is useful if you want to ensure
that there is input data available (and therefore avoid the blocking mechanism).
The mark() method marks the current position in the stream. You can later
return to this position using the reset() method. The mark() and
reset() methods are useful in situations in which you want to read ahead
in the stream but not lose your original position. An example of this situation is
verifying a file type, such as an image file. You would probably read the file header
first and mark the position at the end of the header. You would then read some of
the data to make sure that it follows the format expected for that file type. If
the data doesn't look right, you can reset the read pointer and try a different technique.
Notice that the mark() method takes an integer parameter, readlimit.
readlimit specifies how many bytes can be read before the mark becomes invalidated.
In effect, readlimit determines how far you can read ahead and still be able to reset
the marked position. The markSupported() method returns a boolean value
representing whether or not an input stream supports the mark/reset functionality.
Finally, the close() method closes an input stream and releases any resources
associated with the stream. It is not necessary to explicitly call close()
because input streams are automatically closed when the InputStream object
is destroyed. Although it is not necessary, calling close() immediately
after you are finished using a stream is a good programming practice. The reason
is that close() causes the stream buffer to be flushed, which helps avoid
file corruption.
The System.in Object
The keyboard is the standard device for retrieving user input. The System
class contained in the language package contains a member variable that represents
the keyboard, or standard input stream. This member variable is called in
and is an instance of the InputStream class. This variable is useful for
reading user input from the keyboard. Listing 12.1 contains the ReadKeys1
program, which shows how the System.in object can be connected with an InputStreamReader
object to read user input from the keyboard. This program can be found in the file
ReadKeys1.java on the CD-ROM that accompanies this book.
NOTE: I mentioned the keyboard as being
the standard input stream. This isn't entirely true because the standard input stream
can receive input from any number of sources. Although the keyboard certainly is
the most common method of feeding input to the standard input stream, it is not the
only method. An example of the standard input stream being driven by a different
input source is the redirection of an input file into a stream.
Listing 12.1. The ReadKeys1 class.
import java.io.*;
class ReadKeys1 {
public static void main (String args[]) {
StringBuffer s = new StringBuffer();
char c;
try {
Reader in = new InputStreamReader(System.in);
while ((c = (char)in.read()) != `\n') {
s.append(c);
}
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
System.out.println(s);
}
}
The ReadKeys1 class first creates a StringBuffer object called
s. It then creates a reader object connected to the standard input stream
and enters a while loop that repeatedly calls the read() method
until a newline character (\n) is detected (that is, the user presses Return).
Notice that the input data returned by read() is cast to a char
type before being stored in the character variable c. Each time a character
is read, it is appended to the string buffer using the append() method of
StringBuffer. It is important to see how any errors caused by the read()
method are handled by the try/catch exception-handling blocks.
The catch block simply prints an error message to the standard output stream
based on the error that occurred. Finally, when a newline character is read from
the input stream, the println() method of the standard output stream is
called to output the string to the screen. You learn more about the standard output
stream a little later in this chapter.
Listing 12.2 contains ReadKeys2, which is similar to ReadKeys1
except that it uses the second version of the read() method. This read()
method takes an array of characters as a parameter to store the input that is read.
ReadKeys2 can be found in the file ReadKeys2.java on the CD-ROM
that accompanies this book.
Listing 12.2. The ReadKeys2 class.
import java.io.*;
class ReadKeys2 {
public static void main (String args[]) {
char buf[] = new char[80];
try {
Reader in = new InputStreamReader(System.in);
in.read(buf, 0, 80);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf);
System.out.println(s);
}
}
In ReadKeys2, an array of characters 80 characters long is created. A reader
object is created and connected to the standard input stream, and a single read()
method call is performed that reads everything the user has typed. The input is blocked
until the user presses Return, at which time the input becomes available and the
read() method fills the character array with the new data. A String
object is then created to hold the constant string previously read. Notice that the
constructor used to create the String object takes an array of characters
(buf) as the first parameter. Finally, println() is again used
to output the string.
The ReadKeys3 program in Listing 12.3 shows how to use the last version
of the read() method. This version of read() again takes an array
of characters, as well as an offset and length for determining how to store the input
data in the character array. ReadKeys3 can be found in the file ReadKeys3.java
on the CD-ROM that accompanies this book.
Listing 12.3. The ReadKeys3 class.
import java.io.*;
class ReadKeys3 {
public static void main (String args[]) {
char buf[] = new char[10];
try {
Reader in = new InputStreamReader(System.in);
in.read(buf, 0, 10);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf);
System.out.println(s);
}
}
ReadKeys3 is very similar to ReadKeys2, with one major difference:
The third version of the read() method is used to limit the maximum number
of characters read into the array. The size of the character array is also shortened
to 10 characters to show what this version of read() does when more data
is available than the array can hold. Remember that this version of read()
can also be used to read data into a specific offset of the array. In this case,
the offset is specified as 0 so that the only difference is the maximum
number of characters that can be read (10). This useful technique guarantees
that you don't overrun the array.
The BufferedInputStream Class
As its name implies, the BufferedInputStream class provides a buffered
stream of input. This means that more data is read into the buffered stream than
you might have requested so that subsequent reads come straight out of the buffer
rather than from the input device. This arrangement can result in much faster read
access because reading from a buffer is really just reading from memory. BufferedInputStream
implements all the same methods defined by InputStream. As a matter of fact,
it doesn't implement any new methods of its own. However, the BufferedInputStream
class does have two different constructors, which follow:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size)
Notice that both constructors take an InputStream object as the first
parameter. The only difference between the two is the size of the internal buffer.
In the first constructor, a default buffer size is used; in the second constructor,
you specify the buffer size with the size integer parameter. To support buffered
input, the BufferedInputStream class also defines a handful of member variables,
which follow:
- byte buf[]
- int count
- int pos
- int markpos
- int marklimit
The buf byte array member is the buffer in which input data is actually stored.
The count member variable keeps up with how many bytes are stored in the buffer.
The pos member variable keeps up with the current read position in the buffer. The
markpos member variable specifies the current mark position in the buffer as set
using the mark() method. markpos is equal to -1 if no mark has
been set. Finally, the marklimit member variable specifies the maximum number of
bytes that can be read before the mark position is no longer valid. marklimit is
set by the readlimit parameter passed into the mark() method. Because all
these member variables are specified as protected, you will probably never
actually use any of them. However, seeing these variables should give you some insight
into how the BufferedInputStream class implements the methods defined by
InputStream.
The BufferedReader class is very similar to the BufferedInputStream
class except that it deals with characters instead of bytes. Listing 12.4 contains
the ReadKeys4 program, which uses a BufferedReader object instead
of an InputStreamReader object to read input from the keyboard. ReadKeys4
can be found in the file ReadKeys4.java on the CD-ROM that accompanies this
book.
Listing 12.4. The ReadKeys4 class.
import java.io.*;
class ReadKeys4 {
public static void main (String args[]) {
Reader in = new BufferedReader(new InputStreamReader(System.in));
char buf[] = new char[10];
try {
in.read(buf, 0, 10);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf);
System.out.println(s);
}
}
The BufferedReader object is created by passing the System.in input
stream into an InputStreamReader object. This approach is necessary because
the BufferedReader() constructor requires a Reader-derived object.
From there on, the program is essentially the same as ReadKeys3, except
that the read() method is called on the BufferedReader object rather
than on an InputStreamReader object.
The DataInputStream Class
The DataInputStream class is useful for reading primitive Java data types
from an input stream in a portable fashion. There is only one constructor for DataInputStream,
which simply takes an InputStream object as its only parameter. This constructor
is defined as follows:
DataInputStream(InputStream in)
DataInputStream implements the following useful methods beyond those
defined by InputStream:
- final int skipBytes(int n)
- final void readFully(byte b[])
- final void readFully(byte b[], int off, int len)
- final boolean readBoolean()
- final byte readByte()
- final int readUnsignedByte()
- final short readShort()
- final int readUnsignedShort()
- final char readChar()
- final int readInt()
- final long readLong()
- final float readFloat()
- final double readDouble()
The skipBytes() method works in a manner very similar to skip();
the exception is that skipBytes() blocks until all bytes are skipped. The
number of bytes to skip is determined by the integer parameter n. There are two readFully()
methods implemented by DataInputStream. These methods are similar to the
read() methods except that they block until all data has been read. The
normal read() methods block only until some data is available, not all.
The readFully() methods are to the read() methods what skipBytes()
is to skip().
The rest of the methods implemented by DataInputStream are variations
of the read() method for different fundamental data types. The type read
by each method is easily identifiable by the name of the method.
The FileInputStream Class
The FileInputStream class is useful for performing simple file input.
For more advanced file input operations, you will more than likely want to use the
RandomAccessFile class, discussed a little later in this chapter. The FileInputStream
class can be instantiated using one of the following three constructors:
- FileInputStream(String name)
- FileInputStream(File file)
- FileInputStream(FileDescriptor fdObj)
The first constructor takes a String object parameter called name, which
specifies the name of the file to use for input. The second constructor takes a File
object parameter that specifies the file to use for input. You learn more about the
File object near the end of this chapter. The third constructor for FileInputStream
takes a FileDescriptor object as its only parameter.
The FileInputStream class functions exactly like the InputStream
class except that it is geared toward working with files. Similarly, the FileReader
class functions like the FileInputStream class except that it operates on
characters instead of bytes. List- ing 12.5 contains the ReadFile program,
which uses the FileReader class to read data from a text file. ReadFile
can be found in the file ReadFile.java on the CD-ROM that accompanies this
book.
Listing 12.5. The ReadFile class.
import java.io.*;
class ReadFile {
public static void main (String args[]) {
char buf[] = new char[64];
try {
Reader in = new FileReader("Grocery.txt");
in.read(buf, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf);
System.out.println(s);
}
}
In ReadFile, a FileReader object is first created by passing a
string with the name of the file ("Grocery.txt") as the input
file. The read() method is then called to read from the input file into
a character array. The character array is then used to create a String object,
which is in turn used for output. Pretty simple!
The StringBufferInputStream Class
Aside from having a very long name, the StringBufferInputStream class
is a pretty neat class. StringBufferInputStream enables you to use a string
as a buffered source of input. StringBufferInputStream implements all the
same methods defined by InputStream, and no more. The StringBufferInputStream
class has a single constructor, which follows:
StringBufferInputStream(String s)
The constructor takes a String object, from which it constructs the string
buffer input stream. Although StringBufferInputStream doesn't define any
additional methods, it does provide a few of its own member variables, which follow:
- String buffer
- int count
- int pos
The buffer string member is the buffer in which the string data is actually stored.
The count member variable specifies the number of characters to use in the buffer.
Finally, the pos member variable keeps up with the current position in the buffer.
As is true with the BufferedInputStream class, you will probably never see
these member variables, but they are important in understanding how the StringBufferInputStream
class is implemented.
The StringReader class is a character version of the StringBufferInputStream
class. Listing 12.6 shows the ReadString program, which uses a StringReader
object to read data from a string of text data. ReadString can be found
in the file ReadString.java on the CD-ROM that accompanies this book.
Listing 12.6. The ReadString class.
import java.io.*;
class ReadString {
public static void main (String args[]) {
// Get a string of input from the user
char buf1[] = new char[64];
try {
Reader in = new InputStreamReader(System.in);
in.read(buf1, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s1 = new String(buf1);
// Read the string and output it
Reader in = new StringReader(s1);
char buf2[] = new char[64];
try {
in.read(buf2, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s2 = new String(buf2);
System.out.println(s2);
}
}
The ReadString program enables the user to type text, which is read and
stored in a string. This string is then used to create a StringReader object
that is read into another string for output. Obviously, this program goes to a lot
of trouble to do very little; it's meant only as a demonstration of how to use the
StringReader class. It's up to you to find an interesting application to
which you can apply this class.
The first half of the ReadString program should look pretty familiar;
it's essentially the guts of the ReadKeys3 program (discussed earlier in
this chapter), which reads data entered by the keyboard into a string. The second
half of the program is where you actually get busy with the StringReader
object. A StringReader object is created using the String object
(s1) containing the text entered from the keyboard. The contents of the
StringReader object are then read into a character array using the read()
method. The character array is in turn used to construct another String
object (s2), which is output to the screen.
Output Stream and Writer Classes
In Java, output streams are the logical counterparts to input streams; they handle
writing data to output sources. Using the water analogy from the discussion of input
streams earlier in this chapter, an output stream is equivalent to the water spout
on your bathtub. Just as water travels from a water plant through the pipes and out
the spout into your bathtub, so must data flow from an input device through the operating
system and out an output device. A leaky water spout is an even better way to visualize
the transfer of data out of an output stream; each drop of water falling out of the
spout represents a byte of data. Each byte of data flows to the output device just
like the drops of water falling one after the other out of the bathtub spout.
Getting back to Java, you use output streams to output data to various output
devices, such as the screen. The primary output stream classes used in Java programming
follow:
- OutputStream
- PrintStream
- BufferedOutputStream
- DataOutputStream
- FileOutputStream
The Java output streams provide a variety of ways to output data. The OutputStream
class defines the core behavior required of an output stream. The PrintStream
class is geared toward outputting text data, such as the data sent to the standard
output stream. The BufferedOutputStream class is an extension to the OutputStream
class that provides support for buffered output. The DataOutputStream class
is useful for outputting primitive data types such as int or float.
Finally, the FileOutputStream class provides the support necessary to output
data to files.
Java also supports character output streams, which are virtually identical to
the output streams just listed except that they operate on characters rather than
bytes. The character output stream classes are called writers instead of output streams.
A corresponding writer class implements methods similar to the output stream classes
just listed except for the DataOutputStream class. The purpose of providing
character-based versions of the output stream classes is to help facilitate the internationalization
of character information. To find out more about the support for internationalization
in Java, check out Chapter 52, "Java Internationalization." Following are
the basic writer classes provided by Java:
- Writer
- PrintWriter
- BufferedWriter
- FileWriter
Throughout this chapter, you use both output streams and writer classes. Because
their supported methods are very similar, it's pretty easy to use both types after
you learn about one of them.
The OutputStream Class
The OutputStream class is the output counterpart to InputStream;
it serves as an abstract base class for all the other output stream classes. OutputStream
defines the basic protocol for writing streamed data to an output device. As you
did with the methods for InputStream, you will become accustomed to the
methods defined by OutputStream because they act very much the same in every
OutputStream-derived class. The benefit of this common interface is that
you can essentially learn a method once and then be able to apply it to different
classes without starting the learning process over again.
You typically create an OutputStream-derived object and call an appropriate
method to tell it you want to output information. The OutputStream class
uses a technique similar to the one used by InputStream: It blocks until
data has been written to an output device. While blocking (waiting for the current
output to be processed), the OutputStream class does not allow any further
data to be output.
The OutputStream class implements the following methods:
- abstract void write(int b)
- void write(byte b[])
- void write(byte b[], int off, int len)
- void flush()
- void close()
OutputStream defines three different write() methods for writing
data in a few different ways. The first write() method writes a single byte
to the output stream, as specified by the integer parameter b. The second version
of write() takes an array of bytes as a parameter and writes them to the
output stream. The last version of write() takes a byte array, an integer
offset, and a length as parameters. This version of write() is very much
like the second version except that it uses the other parameters to determine where
in the byte array to begin outputting data, along with how much data to output. The
off parameter specifies an offset into the byte array from which you want to begin
outputting data, and the len parameter specifies how many bytes are to be output.
The flush() method is used to flush the output stream. Calling flush()
forces the OutputStream object to output any pending data.
Finally, the close() method closes an output stream and releases any
resources associated with the stream. As with InputStream objects, it isn't
usually necessary to call close() on an OutputStream object because
streams are automatically closed when they are destroyed.
The PrintStream Class
The PrintStream class is derived from OutputStream and is designed
primarily for printing output data as text. PrintStream has two constructors:
- PrintStream(OutputStream out)
- PrintStream(OutputStream out, boolean autoflush)
Both PrintStream constructors take an OutputStream object as
their first parameter. The only difference between the two methods is how the newline
character is handled. In the first constructor, the stream is flushed based on an
internal decision by the object. In the second constructor, you can specify that
the stream be flushed every time it encounters a newline character. You specify this
through the boolean autoflush parameter.
The PrintStream class also implements a rich set of methods, which follow:
- boolean checkError()
- void print(Object obj)
- synchronized void print(String s)
- synchronized void print(char s[])
- void print(char c)
- void print(int i)
- void print(long l)
- void print(float f)
- void print(double d)
- void print(boolean b)
- void println()
- synchronized void println(Object obj)
- synchronized void println(String s)
- synchronized void println(char s[])
- synchronized void println(char c)
- synchronized void println(int I)
- synchronized void println(long l)
- synchronized void println(float f)
- synchronized void println(double d)
- synchronized void println(boolean b)
The checkError() method flushes the stream and returns whether or not
an error has occurred. The return value of checkError() is based on whether
an error has ever occurred on the stream, meaning that once an error occurs, checkError()
always returns true for that stream.
PrintStream provides a variety of print() methods to handle
all your printing needs. The version of print() that takes an Object
parameter simply outputs the results of calling the toString() method on
the object. Each of the other print() methods takes a different type parameter
that specifies which data type is to be printed.
The println() methods implemented by PrintStream are very similar
to the print() methods. The only difference is that the println()
methods print a newline character following the data that is printed. The println()
method that takes no parameters simply prints a newline character by itself.
The System.out Object
The monitor is the primary output device on modern computer systems. The System
class has a member variable that represents the standard output stream, which is
typically the monitor. The member variable is called out and is an instance
of the PrintStream class. The out member variable is very useful
for outputting text to the screen. But you already know this because you've seen
the out member variable in most of the sample programs developed thus far.
The BufferedOutputStream Class
The BufferedOutputStream class is very similar to the OutputStream
class except that it provides a buffered stream of output. This class enables you
to write to a stream without causing a bunch of writes to an output device. The BufferedOutputStream
class maintains a buffer that is written to when you write to the stream. When the
buffer gets full or is explicitly flushed, it is written to the output device. This
output approach is much more efficient because most of the data transfer takes place
in memory. And when it does come time to output the data to a device, it all happens
at once.
The BufferedOutputStream class implements the same methods defined in
OutputStream, meaning that there are no additional methods except for constructors.
The two constructors for BufferedOutputStream follow:
- BufferedOutputStream(OutputStream out)
- BufferedOutputStream(OutputStream out, int size)
Both constructors for BufferedOutputStream take an OutputStream
object as their first parameter. The only difference between the two is the size
of the internal buffer used to store the output data. In the first constructor, a
default buffer size of 512 bytes is used; in the second constructor, you specify
the buffer size with the size integer parameter. The buffer itself within the BufferedOutputStream
class is managed by two member variables, which follow:
The buf byte array member variable is the actual data buffer in which output data
is stored. The count member keeps up with how many bytes are in the buffer. These
two member variables are sufficient to represent the state of the output stream buffer.
The BufferedWriter class is a character-based version of the BufferedOutputStream
class. Listing 12.7 shows the WriteStuff program, which uses a BufferedWriter
object to output a character array of text data. WriteStuff can be found
in the file WriteStuff.java on the CD-ROM that accompanies this book.
Listing 12.7. The WriteStuff class.
import java.io.*;
class WriteStuff {
public static void main (String args[]) {
// Copy the string into a byte array
String s = new String("Dance, spider!\n");
char[] buf = new char[64];
s.getChars(0, s.length(), buf, 0);
// Output the byte array (buffered)
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
try {
out.write(buf, 0, 64);
out.flush();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
The WriteStuff program fills a character array with text data from a string
and outputs the array to the screen using a buffered output stream. WriteStuff
begins by creating a String object containing text, and a character array.
The getChars() method of String is used to copy the characters
of data in the string to the character array. The getChars() method copies
each character in the string to the array. Once the character array is ready, a BufferedWriter
object is created by passing System.out into the constructor for an OutputStreamWriter
object, which in turn is passed to the constructor for the BufferedWriter
object. The character array is then written to the output buffer using the write()
method. Because the stream is buffered, it is necessary to call the flush()
method to actually output the data.
The DataOutputStream Class
The DataOutputStream class is useful for writing primitive Java data
types to an output stream in a portable way. DataOutputStream has only one
constructor, which simply takes an OutputStream object as its only parameter.
This constructor is defined as follows:
DataOutputStream(OutputStream out)
The DataOutputStream class implements the following useful methods beyond
those inherited from OutputStream:
- final int size()
- final void writeBoolean(boolean v)
- final void writeByte(int v)
- final void writeShort(int v)
- final void writeChar(int v)
- final void writeInt(int v)
- final void writeFloat(float v)
- final void writeDouble(double v)
- final void writeBytes(String s)
- final void writeChars(String s)
The size() method is used to determine how many bytes have been written
to the stream thus far. The integer value returned by size() specifies the
number of bytes written.
The rest of the methods implemented in DataOutputStream are all variations
of the write() method. Each version of writeType() takes
a different data type that is in turn written as output.
The FileOutputStream Class
The FileOutputStream class provides a means to perform simple file output.
For more advanced file output, check out the RandomAccessFile class, discussed
a little later in this chapter. A FileOutputStream object can be created
using one of the following constructors:
- FileOutputStream(String name)
- FileOutputStream(File file)
- FileOutputStream(FileDescriptor fdObj)
The first constructor takes a String parameter, which specifies the name
of the file to use for output. The second constructor takes a File object
parameter, which specifies the output file. You learn about the File object
later in this chapter. The third constructor takes a FileDescriptor object
as its only parameter.
The FileWriter class functions exactly like the FileOutputStream
class except that it is specifically designed to work with characters instead of
with bytes. Listing 12.8 contains the WriteFile program, which uses the
FileWriter class to write user input to a text file. WriteFile
can be found in the file WriteFile.java on the CD-ROM that accompanies this
book.
Listing 12.8. The WriteFile class.
import java.io.*;
class WriteFile {
public static void main (String args[]) {
// Read the user input
char buf[] = new char[64];
try {
Reader in = new InputStreamReader(System.in);
in.read(buf, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
// Output the data to a file
try {
Writer out = new FileWriter("Output.txt");
out.write(buf, 0, 64);
out.flush();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
In WriteFile, user input is read from the standard input stream into a character
array using the read() method of InputStreamReader. A FileWriter
object is then created with the filename Output.txt, which is passed in
as the only parameter to the constructor. The write() method is then used
to output the character array to the stream.
You can see that working with writers is just as easy as working with readers.
File Classes
If the FileInputStream and FileOutputStream classes don't quite
meet your file-handling expectations, don't despair! Java provides two more classes
for working with files that are sure to meet your needs. These two classes are File
and RandomAccessFile. The File class models an operating system
directory entry, providing you with access to information about a file including
file attributes and the full path where the file is located, among other things.
The RandomAccessFile class, on the other hand, provides a variety of methods
for reading and writing data to and from a file.
The File Class
The File class can be instantiated using one of three constructors, which
follow:
- File(String path)
- File(String path, String name)
- File(File dir, String name)
The first constructor takes a single String parameter that specifies
the full path name of the file. The second constructor takes two String
parameters: path and name. The path parameter specifies the directory path where
the file is located; the name parameter specifies the name of the file. The third
constructor is similar to the second except that it takes another File object
as the first parameter instead of a string. The File object in this case
is used to specify the directory path of the file.
The most important methods implemented by the File class follow:
- String getName()
- String getPath()
- String getAbsolutePath()
- boolean exists()
- boolean canWrite()
- boolean canRead()
- boolean isFile()
- boolean isDirectory()
- boolean isAbsolute()
- long lastModified()
- long length()
- boolean mkdir()
- boolean mkdirs()
- boolean renameTo(File dest)
- boolean delete()
- String[] list()
- String[] list(FilenameFilter filter)
The getName() method gets the name of a file and returns it as a string.
The getPath() method returns the path of a file--which may be relative--as
a string. The getAbsolutePath() method returns the absolute path of a file.
The getParent() method returns the parent directory of a file or null
if a parent directory is not found.
The exists() method returns a boolean value that specifies whether or
not a file actually exists. The canWrite() and canRead() methods
return boolean values that specify whether a file can be written to or read from.
The isFile() and isDirectory() methods return boolean values that
specify whether a file is valid and whether the directory information is valid. The
isAbsolute() method returns a boolean value that specifies whether a filename
is absolute.
The lastModified() method returns a long value that specifies
the time at which a file was last modified. The long value returned is useful
only in determining differences between modification times; it has no meaning as
an absolute time and is not suitable for output. The length() method returns
the length of a file in bytes.
The mkdir() method creates a directory based on the current path information.
mkdir() returns a boolean value indicating the success of creating the directory.
The mkdirs() method is similar to mkdir(), except that it can be
used to create an entire directory structure. The renameTo() method renames
a file to the name specified by the File object passed as the dest parameter.
The delete() method deletes a file. Both renameTo() and delete()
return a boolean value indicating the success or failure of the operation.
Finally, the list() methods of the File object obtain listings
of the directory contents. Both list() methods return a list of filenames
in a String array. The only difference between the two methods is that the
second version takes a FilenameFilter object that enables you to filter
out certain files from the list.
Listing 12.9 shows the source code for the FileInfo program, which uses
a File object to determine information about a file in the current directory.
The FileInfo program is located in the FileInfo.java source file
on the CD-ROM that accompanies this book.
Listing 12.9. The FileInfo class.
import java.io.*;
class FileInfo {
public static void main (String args[]) {
System.out.println("Enter file name: ");
char c;
StringBuffer buf = new StringBuffer();
try {
Reader in = new InputStreamReader(System.in);
while ((c = (char)in.read()) != `\n')
buf.append(c);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
File file = new File(buf.toString());
if (file.exists()) {
System.out.println("File Name : " + file.getName());
System.out.println(" Path : " + file.getPath());
System.out.println("Abs. Path : " + file.getAbsolutePath());
System.out.println("Writable : " + file.canWrite());
System.out.println("Readable : " + file.canRead());
System.out.println("Length : " + (file.length() / 1024) + "KB");
}
else
System.out.println("Sorry, file not found.");
}
}
The FileInfo program uses the File object to get information about
a file in the current directory. The user is first prompted to type a filename; the
resulting input is stored in a String object. The String object
is then used as the parameter to the File object's constructor. A call to
the exists() method determines whether the file actually exists. If so,
information about the file is obtained through the various File() methods
and the results are output to the screen.
Following are the results of running FileInfo and specifying FileInfo.java
as the file for which you want to get information:
File Name : FileInfo.java
Path : FileInfo.java
Abs. Path : C:\Books\Jul11PRE\Source\Chap12\FileInfo.java
Writable : true
Readable : true
Length : 0KB
The RandomAccessFile Class
The RandomAccessFile class provides a multitude of methods for reading
and writing to and from files. Although you can certainly use FileInputStream
and FileOutputStream for file I/O, RandomAccessFile provides many
more features and options. Following are the constructors for RandomAccessFile:
- RandomAccessFile(String name, String mode)
- RandomAccessFile(File file, String mode)
The first constructor takes a String parameter specifying the name of
the file to access, along with a String parameter specifying the type of
mode (read or write). The mode type can be either "r" for read
mode or "rw" for read/write mode. The second constructor takes
a File object as the first parameter, which specifies the file to access.
The second parameter is a mode string, which works exactly the same as it does in
the first constructor.
The RandomAccessFile class implements a variety of powerful file I/O
methods. Following are some of the most useful ones:
- int skipBytes(int n)
- long getFilePointer()
- void seek(long pos)
- int read()
- int read(byte b[])
- int read(byte b[], int off, int len)
- final boolean readBoolean()
- final byte readByte()
- final int readUnsignedByte()
- final short readShort()
- final int readUnsignedShort()
- final char readChar()
- final int readInt()
- final long readLong()
- final float readFloat()
- final double readDouble()
- final String readLine()
- final void readFully(byte b[])
- final void readFully(byte b[], int off, int len)
- void write(byte b[])
- void write(byte b[], int off, int len)
- final void writeBoolean(boolean v)
- final void writeByte(int v)
- final void writeShort(int v)
- final void writeChar(int v)
- final void writeInt(int v)
- final void writeLong(long v)
- void writeFloat(float v)
- void writeDouble(double v)
- void writeBytes(String s)
- void writeChars(String s)
- long length()
- void close()
From looking at this method list, you no doubt are thinking that many of these
methods look familiar. And they should look familiar--most of the methods implemented
by RandomAccessFile are also implemented by either FileInputStream
or FileOutputStream. The fact that RandomAccessFile combines them
into a single class is a convenience in and of itself. But you already know how to
use these methods because they work just as they do in the FileInputStream
and FileOutputStream classes. What you are interested in are the new methods
implemented by RandomAccessFile.
The first new method you may have noticed is the getFilePointer() method.
getFilePointer() returns the current position of the file pointer as a long
value. The file pointer indicates the location in the file where data will next be
read from or written to. In read mode, the file pointer is analogous to the needle
on a phonograph or the laser in a CD player. The seek() method is the other
new method that should catch your attention. seek() sets the file pointer
to the absolute position specified by the long parameter pos. Calling seek()
to move the file pointer is analogous to moving the phonograph needle with your hand.
In both cases, the read point of the data or music is being moved. A similar situation
occurs when you are writing data.
NOTE: It's worth noting that Java's file
pointers are not pointers in the traditional sense (that is, a pointer meaning a
direct reference to a memory location). As you may know, Java doesn't support memory
pointers. File pointers are more conceptual in nature: They "point" to
a specific location in a file.
Listing 12.10 shows the source code for FilePrint, a program that uses
the RandomAccessFile class to print a file to the screen. The source code
for the FilePrint program can be found in the file FilePrint.java
on the CD-ROM that accompanies this book.
Listing 12.10. The FilePrint class.
import java.io.*;
class FilePrint {
public static void main (String args[]) {
System.out.println("Enter file name: ");
char c;
StringBuffer buf = new StringBuffer();
try {
Reader in = new InputStreamReader(System.in);
while ((c = (char)in.read()) != `\n')
buf.append(c);
System.out.println(buf.toString());
RandomAccessFile file = new RandomAccessFile(buf.toString(), "rw");
while (file.getFilePointer() < file.length())
System.out.println(file.readLine());
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
The FilePrint program begins very much like the FileInfo program
in Listing 12.9 in that it prompts the user to type a filename and stores the result
in a string. It then uses that string to create a RandomAccessFile object
in read/write mode, which is specified by passing "rw" as the
second parameter to the constructor. A while loop is then used to repeatedly
call the readLine() method until the entire file has been read. The call
to readLine() is performed within a call to println() so that each
line of the file is output to the screen.
Summary
Whew! This chapter covered a lot of ground! Hopefully, you've managed to make
it this far relatively unscathed. On the up side, you've learned just about all there
is to know about fundamental Java I/O and the most important classes in the I/O package.
That's not to say that there isn't still a wealth of information inside the I/O package
that you haven't seen. The point is that the Java class libraries are very extensive,
which means that some of the classes are useful only in very special circumstances.
The goal of this chapter was to highlight the more mainstream classes and methods
within the I/O package.
One of the neatest uses of I/O streams is in sending and receiving data across
a network. You're in luck because the next chapter takes a look at the Java networking
package, java.net. You learn all about the built-in networking support provided
by the networking package and how it can be used to build network Java programs.
|