All Categories :
Java
Chapter 14
The I/O Package
by Michael Morrison
CONTENTS
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.
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 like water is directed through the
complex system of pipes that make up a water system. The data
in an input stream is transmitted a 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
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
will be 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 for this is that close()
causes the stream buffer to be flushed, which helps avoid file
corruption.
The System.in
Object
The keyboard is the most 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 14.1 contains the ReadKeys1
program, which shows how the System.in
object can be used with the first version of the read()
method. 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 totally 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 14.1. The ReadKeys1
class.
class ReadKeys1 {
public static void main (String args[]) {
StringBuffer s = new StringBuffer();
char c;
try {
while ((c = (char)System.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 enters
a while loop that repeatedly
calls the read method until
a newline character is detected (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 14.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 bytes 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 14.2. The ReadKeys2
class.
class ReadKeys2 {
public static void main (String args[]) {
byte buf[] = new byte[80];
try {
System.in.read(buf);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf, 0);
System.out.println(s);
}
}
In ReadKeys2, an array of
bytes is created that is 80 bytes long. 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 byte 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 bytes (buf)
as the first parameter and appends the high-byte value specified
in the second parameter to each byte, thus forming 16-bit Unicode
characters. Because the standard ASCII characters map to Unicode
characters with zeros in the high byte, passing 0
as the high byte to the constructor works perfectly. Finally,
println() is again used to
output the string.
The ReadKeys3 program in
Listing 14.3 shows how to use the last version of the read()
method. This version of read()
again takes an array of bytes, as well as an offset and length
for determining how to store the input data in the byte array.
ReadKeys3 can be found in
the file ReadKeys3.java on
the CD-ROM that accompanies this book.
Listing 14.3. The ReadKeys3
class.
class ReadKeys3 {
public static void main (String args[]) {
byte buf[] = new byte[10];
try {
System.in.read(buf, 0, 10);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf, 0);
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 bytes read into
the array. The size of the byte array is also shortened to 10
bytes to show how this version of read()
handles it 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 bytes that
can be read (10). This useful
technique guarantees that you don't overrun a byte 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.
Listing 14.4 contains the ReadKeys4
program, which uses a BufferedInputStream
object instead of System.in
to read input from the keyboard. ReadKeys4
can be found in the file ReadKeys4.java
on the CD-ROM that accompanies this book.
Listing 14.4. The ReadKeys4
class.
import java.io.*;
class ReadKeys4 {
public static void main (String args[]) {
BufferedInputStream in = new BufferedInputStream(System.in);
byte buf[] = new byte[10];
try {
in.read(buf, 0, 10);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf, 0);
System.out.println(s);
}
}
Notice first that the BufferedInputStream
class must be imported from the I/O package. Actually, in this
case, the * qualifier is
used to import all the classes in the I/O package. The BufferedInputStream
object is created by passing the System.in
InputStream into its constructor.
From there on, the program is essentially the same as ReadKeys3,
except that the read() method
is called on the BufferedInputStream
object rather than on System.in.
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 String readLine()
- 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 readLine() method is
used to read a line of text that has been terminated with a newline
(\n), carriage return (\r),
carriage return/newline (\r\n),
or end-of-file (EOF) character
sequence. readLine() returns
the line of text in a String
object. Listing 14.5 contains the ReadFloat
program, which uses the readLine()
method to read a floating-point value from the user.
Listing 14.5. The ReadFloat class.
import java.io.*;
class ReadFloat {
public static void main (String args[]) {
DataInputStream in = new DataInputStream(System.in);
String s = new String();
try {
s = in.readLine();
float f = Float.valueOf(s).floatValue();
System.out.println(f);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
In ReadFloat, a DataInputStream
object is first created based on System.in.
A String object is then created
to hold the input line of text. The readLine()
method is called with the resulting line of text being stored
in the String object s.
A floating-point number is extracted from the string by first
getting a Float object from
the string using the valueOf()
static method of the Float
class. The floatValue() method
is then called on the Float
object to get a float value,
which is then stored in the float
variable f. This value is
then output to the screen using println().
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 in three following
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. Listing
14.6 contains the ReadFile
program, which uses the FileInputStream
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 14.6. The ReadFile class.
import java.io.*;
class ReadFile {
public static void main (String args[]) {
byte buf[] = new byte[64];
try {
FileInputStream in = new FileInputStream("Grocery.txt");
in.read(buf, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s = new String(buf, 0);
System.out.println(s);
}
}
In ReadFile, a FileInputStream
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 byte
array. The byte 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 where 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.
Listing 14.7 shows the ReadString
program, which uses a StringBufferInputStream
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 14.7. The ReadString class.
import java.io.*;
class ReadString {
public static void main (String args[]) {
// Get a string of input from the user
byte buf1[] = new byte[64];
try {
System.in.read(buf1, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s1 = new String(buf1, 0);
// Read the string as a string buffer and output it
StringBufferInputStream in = new StringBufferInputStream(s1);
byte buf2[] = new byte[64];
try {
in.read(buf2, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
String s2 = new String(buf2, 0);
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 StringBufferInputStream
that is read into another string for output. Obviously, this program
goes to a lot of trouble to do very little; it's only meant as
a demonstration of how to use the StringBufferInputStream
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,
which reads data entered by the keyboard into a string. The second
half of the program is where you actually get busy with the StringBufferInputStream
object. A StringBufferInputStream
object is created using the String
object (s1) containing the
text entered from the keyboard. The contents of the StringBufferInputStream
object are then read into a byte array using the read()
method. The byte array is in turn used to construct another String
object (s2), which is output
to the screen.
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. And finally, the
FileOutputStream class provides
the support necessary to output data to files.
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 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 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 will block 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 it 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 different 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 an error ever having 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. The other print()
methods each take a different type parameter that specifies which
data type is 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 when it 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.
Listing 14.8 shows the WriteStuff
program, which uses a BufferedOutputStream
object to output a byte array of text data. WriteStuff
can be found in the file WriteStuff.java
on the CD-ROM that accompanies this book.
Listing 14.8. 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");
byte[] buf = new byte[64];
s.getBytes(0, s.length(), buf, 0);
// Output the byte array (buffered)
BufferedOutputStream out = new BufferedOutputStream(System.out);
try {
out.write(buf, 0, 64);
out.flush();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
The WriteStuff program fills
a byte array with text data from a string and outputs the byte
array to the screen using a buffered output stream. WriteStuff
begins by creating a String
object containing text, and a byte array. The getBytes()
method of String is used
to copy the bytes of data in the string to the byte array. The
getBytes() method copies
the low byte of each character in the string to the byte array.
This works because the Unicode representation of ASCII characters
has zeros in the high byte. Once the byte array is ready, a BufferedOutputStream
object is created by passing System.out
into the constructor. The byte 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 writeLong(long 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 on 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, you should 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 input.
The second constructor takes a File
object parameter that specifies the input file. You learn about
the File object later in
this chapter. The third constructor takes a FileDescriptor
object as its only parameter.
The FileOutputStream class
functions exactly like the OutputStream
class except that it is specifically designed to work with files.
Listing 14.9 contains the WriteFile
program, which uses the FileOutputStream
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 14.9. The WriteFile class.
import java.io.*;
class WriteFile {
public static void main (String args[]) {
// Read the user input
byte buf[] = new byte[64];
try {
System.in.read(buf, 0, 64);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
// Output the data to a file
try {
FileOutputStream out = new FileOutputStream("Output.txt");
out.write(buf);
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
In WriteFile, user input
is read from the standard input stream into a byte array using
the read() method of InputStream.
A FileOutputStream 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 byte array to the stream.
You can see that working with output file streams is just as easy
as working with input file streams.
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 pathname 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()
- String getParent()
- 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 only useful 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 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 is that the second
version takes a FilenameFilter
object that enables you to filter out certain files from the list.
Listing 14.10 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 14.10. 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 {
while ((c = (char)System.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\JavaUnleashedProRef\Source\Chap14\FileInfo.java
Writable : true
Readable : true
Length : 0KB
The RandomAccessFile
Class
The RandomAccessFile class
provides a multitude of methods for reading and writing to 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 like they do in FileInputStream
and FileOutputStream. 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.
It is a similar situation when you are writing data as well.
Listing 14.11 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 14.11. 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 {
while ((c = (char)System.in.read()) != '\n')
buf.append(c);
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 14.10 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.
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 almost 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.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.