Contents:
Input Streams and Readers
Output
Streams and Writers
File
Manipulation
The java.io
package contains the fundamental classes for performing input and output
operations in Java. These I/O classes can be divided into four basic groups:
All fundamental I/O in Java is based on streams. A stream
represents a flow of data, or a channel of communication. Conceptually, there
is a reading process at one end of the stream and a writing process at the
other end. Java 1.0 supported only byte streams, which meant that Unicode
characters were not always handled correctly. As of Java 1.1, there are classes
in java.io
for both byte streams and character streams. The character stream classes,
which are called readers and writers, handle Unicode characters
appropriately.
The rest of this chapter describes the classes in java.io that
read from and write to streams, as well as the classes that manipulate files.
The classes for serializing objects are described in Chapter
7, Object Serialization.
The InputStream
class is an abstract
class that defines methods to read sequentially from a stream of bytes. Java
provides subclasses of the InputStream
class for reading from files, StringBuffer objects, and byte arrays, among other
things. Other subclasses of InputStream
can be chained together to provide additional logic, such as keeping track of
the current line number or combining multiple input sources into one logical
input stream. It is also easy to define a subclass of InputStream that
reads from any other kind of data source.
In Java 1.1, the Reader class is an abstract class that defines methods to
read sequentially from a stream of characters. Many of the byte-oriented InputStream
subclasses have character-based Reader counterparts. Thus, there are subclasses of Reader for
reading from files, character arrays, and String objects.
The InputStream
class is the abstract
superclass of all other byte input stream classes. It defines three read() methods
for reading from a raw stream of bytes:
read()
read(byte[] b)
read(byte[] b, int off, int len)
If there is no data available to read, these methods block until
input is available. The class also defines an available() method that returns the
number of bytes that can be read without blocking and a skip() method
that skips ahead a specified number of bytes. The InputStream class defines a
mechanism for marking a position in the stream and returning to it later, via
the mark()
and reset()
methods. The markSupported()
method returns true
in subclasses that support these methods.
Because the InputStream
class is abstract,
you cannot create a "pure" InputStream. However, the various
subclasses of InputStream
can be used interchangeably. For example, methods often take an InputStream as a
parameter. Such a method accepts any subclass of InputStream as an argument.
InputStream
is designed so that read(byte[])
and read(byte[],
int, int) both call read(). Thus,
when you subclass InputStream,
you only need to define the read()
method. However, for efficiency's sake, you should also override read(byte[], int, int) with a
method that can read a block of data more efficiently than reading each byte
separately.
The Reader
class is the abstract
superclass of all other character input stream classes. It defines nearly the
same methods as InputStream,
except that the read()
methods deal with characters instead of bytes:
read()
read(char[] cbuf)
read(char[] cbuf, int off, int len)
The available()
method of InputStream
has been replaced by the ready()
method of Reader,
which simply returns a flag that indicates whether or not the stream must block
to read the next character.
Reader
is designed so that read()
and read(char[])
both call read(char[],
int, int). Thus, when
you subclass Reader,
you only need to define the read(char[],
int, int) method.
Note that this design is different from, and more efficient than, that of InputStream.
The InputStreamReader
class serves as a bridge between InputStream objects and Reader objects. Although an InputStreamReader
acts like a character stream, it gets its input from an underlying byte stream
and uses a character encoding scheme to translate bytes into characters. When
you create an InputStreamReader,
specify the underlying InputStream
and, optionally, the name of an encoding scheme. For example, the following
code fragment creates an InputStreamReader
that reads characters from a file that is encoded using the ISO 8859-5
encoding:
String fileName = "encodedfile.txt"; String encodingName = "8859_5";
InputStreamReader in;
try {
x FileInputStream fileIn = new FileInputStream(fileName);
in = new InputStreamReader(fileIn, encodingName);
} catch (UnsupportedEncodingException e1) {
System.out.println(encodingName + " is not a supported encoding scheme.");
} catch (IOException e2) {
System.out.println("The file " + fileName + " could not be opened.");
}
The FileInputStream
class is a subclass of InputStream
that allows a stream of bytes to be read from a file. The FileInputStream
class has no explicit open method. Instead, the file is implicitly opened, if
appropriate, when the FileInputStream
is created. There are three ways to create a FileInputStream:
· FileInputStream f1 = new FileInputStream("foo.txt");
· File f = new File("foo.txt");
· FileInputStream f2 = new FileInputStream(f);
· RandomAccessFile raf;
· raf = new RandomAccessFile("z.txt","r");
· FileInputStream f3 = new FileInputStream(raf.getFD());
The FileReader class is a subclass of Reader that
reads a stream of characters from a file. The bytes in the file are converted
to characters using the default character encoding scheme. If you do not want
to use the default encoding scheme, you need to wrap an InputStreamReader
around a FileInputStream,
as shown above. You can create a FileReader from a filename, a File object, or
a FileDescriptor
object, as described above for FileInputStream.
The StringReader
class is a subclass of Reader
that gets its input from a String
object. The StringReader
class supports mark-and-reset functionality via the mark() and reset() methods.
The following example shows the use of StringReader:
StringReader sr = new StringReader("abcdefg");
try {
char[] buffer = new char[3];
sr.read(buffer);
System.out.println(buffer);
} catch (IOException e) {
System.out.println("There was an error while reading.");
}
This code fragment produces the following output:
abc
The StringBufferInputStream
class is the byte-based relative of StringReader. The entire class is
deprecated as of Java 1.1 because it does not properly convert the characters
of the string to a byte stream; it simply chops off the high eight bits of each
character. Although the markSupported()
method of StringBufferInputStream
returns false,
the reset()
method causes the next read operation to read from the beginning of the String.
The CharArrayReader
class is a subclass of Reader
that reads a stream of characters from an array of characters. The CharArrayReader
class supports mark-and-reset functionality via the mark() and reset() methods.
You can create a CharArrayReader
by passing a reference to a char
array to a constructor like this:
char[] c;
...
CharArrayReader r;
r = new CharArrayReader(c);
You can also create a CharArrayReader that only reads from
part of an array of characters by passing an offset and a length to the
constructor. For example, to create a CharArrayReader that reads elements 5
through 24 of a char
array you would write:
r = new CharArrayReader(c, 5, 20);
The ByteArrayInputStream
class is just like CharArrayReader,
except that it deals with bytes instead of characters. In Java 1.0, ByteArrayInputStream
did not fully support mark()
and reset();
in Java 1.1 these methods are completely supported.
The PipedInputStream
class is a subclass of InputStream
that facilitates communication between threads. Because it reads bytes written
by a connected PipedOutputStream,
a PipedInputStream
must be connected to a PipedOutputStream
to be useful. There are a few ways to connect a PipedInputStream to a PipedOutputStream.
You can first create the PipedOutputStream
and pass it to the PipedInputStream
constructor like this:
PipedOutputStream po = new PipedOutputStream();
PipedInputStream pi = new PipedInputStream(po);
You can also create the PipedInputStream first and pass it to
the PipedOutputStream
constructor like this:
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream(pi);
The PipedInputStream
and PipedOutputStream
classes each have a connect()
method you can use to explicitly connect a PipedInputStream and a PipedOutputStream
as follows:
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream();
pi.connect(po);
Or you can use connect() as follows:
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream();
po.connect(pi);
Multiple PipedOutputStream
objects can be connected to a single PipedInputStream at one time, but the
results are unpredictable. If you connect a PipedOutputStream to an already
connected PipedInputStream,
any unread bytes from the previously connected PipedOutputStream are lost. Once the two
PipedOutputStream
objects are connected, the PipedInputStream
reads bytes written by either PipedOutputStream in the order that it receives
them. The scheduling of different threads may vary from one execution of the
program to the next, so the order in which the PipedInputStream receives bytes from
multiple PipedOutputStream
objects can be inconsistent.
The PipedReader
class is the character-based equivalent of PipedInputStream. It works in the same
way, except that a PipedReader
is connected to a PipedWriter
to complete the pipe, using either the appropriate constructor or the connect()
method.
The FilterInputStream
class is a wrapper class for InputStream objects. Conceptually, an object that
belongs to a subclass of FilterInputStream
is wrapped around another InputStream
object. The constructor for this class requires an InputStream. The constructor sets
the object's in
instance variable to reference the specified InputStream, so from that point on, the FilterInputStream
is associated with the given InputStream. All of the methods in FilterInputStream
work by calling the corresponding methods in the underlying InputStream.
Because the close()
method of a FilterInputStream
calls the close()
method of the InputStream
that it wraps, you do not need to explicitly close the underlying InputStream.
A FilterInputStream
does not add any functionality to the object that it wraps, so by itself it is
not very useful. However, subclasses of the FilterInputStream class do add
functionality to the objects that they wrap in two ways:
The FilterReader
class is the character-based equivalent of FilterInputStream. A FilterReader is
wrapped around an underlying Reader object; the methods of FilterReader
call the corresponding methods of the underlying Reader. However, unlike FilterInputStream,
FilterReader
is an abstract
class, so you cannot instantiate it directly.
The DataInputStream
class is a subclass of FilterInputStream
that provides methods for reading a variety of data types. The DataInputStream
class implements the DataInput
interface, so it defines methods for reading all of the primitive Java data
types.
You create a DataInputStream
by passing a reference to an underlying InputStream to the constructor. Here is
an example that creates a DataInputStream
and uses it to read an int
that represents the length of an array and then to read the array of long values:
long[] readLongArray(InputStream in) throws IOException {
DataInputStream din = new DataInputStream(in);
int count = din.readInt();
long[] a = new long[count];
for (int i = 0; i < count; i++) {
a[i] = din.readLong();
}
return a;
}
The BufferedReader
class is a subclass of Reader
that buffers input from an underlying Reader. A BufferedReader object reads
enough characters from its underlying Reader to fill a relatively large
buffer, and then it satisfies read operations by supplying characters that are
already in the buffer. If most read operations read just a few characters,
using a BufferedReader
can improve performance because it reduces the number of read operations that
the program asks the operating system to perform. There is generally a
measurable overhead associated with each call to the operating system, so
reducing the number of calls into the operating system improves performance.
The BufferedReader
class supports mark-and-reset functionality via the mark() and reset() methods.
Here is an example that shows how to create a BufferedReader
to improve the efficiency of reading from a file:
try {
FileReader fileIn = new FileReader("data.dat");
BufferedReader in = new BufferedReader(fileIn);
// read from the file
} catch (IOException e) {
System.out.println(e);
}
The BufferedInputStream
class is the byte-based counterpart of BufferedReader. It works in the same way
as BufferedReader,
except that it buffers input from an underlying InputStream.
The LineNumberReader
class is a subclass of BufferedReader.
Its read()
methods contain additional logic to count end-of-line characters and thereby
maintain a line number. Since different platforms use different characters to
represent the end of a line, LineNumberReader takes a flexible approach and
recognizes "\n",
"\r",
or "\r\n"
as the end of a line. Regardless of the end-of-line character it reads, LineNumberReader
returns only "\n"
from its read()
methods.
You can create a LineNumberReader by passing its constructor a Reader. The
following example prints out the first five lines of a file, with each line
prefixed by its number. If you try this example, you'll see that the line
numbers begin at 0
by default:
try {
FileReader fileIn = new FileReader("text.txt");
LineNumberReader in = new LineNumberReader(fileIn);
for (int i = 0; i < 5; i++)
System.out.println(in.getLineNumber() + " " + in.readLine());
}catch (IOException e) {
System.out.println(e);
}
The LineNumberReader
class has two methods pertaining to line numbers. The getLineNumber()
method returns the current line number. If you want to change the current line
number of a LineNumberReader,
use setLineNumber().
This method does not affect the stream position; it merely sets the value of
the line number.
The LineNumberInputStream
is the byte-based equivalent of LineNumberReader. The entire class is deprecated in
Java 1.1 because it does not convert bytes to characters properly. Apart from
the conversion problem, LineNumberInputStream
works the same as LineNumberReader,
except that it takes its input from an InputStream instead of a Reader.
The SequenceInputStream
class is used to sequence together multiple InputStream objects. Consider this
example:
FileInputStream f1 = new FileInputStream("data1.dat");
FileInputStream f2 = new FileInputStream("data2.dat");
SequenceInputStream s = new SequenceInputStream(f1, f2);
This example creates a SequenceInputStream that reads all of
the bytes from f1
and then reads all of the bytes from f2 before reporting that it has
encountered the end of the stream. You can also cascade SequenceInputStream
object themselves, to allow more than two input streams to be read as if they
were one. You would write it like this:
FileInputStream f3 = new FileInputStream("data3.dat");
SequenceInputStream s2 = new SequenceInputStream(s, f3);
The SequenceInputStream
class has one other constructor that may be more appropriate for wrapping more
than two InputStream
objects together. It takes an Enumeration of InputStream objects as its argument. The
following example shows how to create a SequenceInputStream in this manner:
Vector v = new Vector();
v.add(new FileInputStream("data1.dat"));
v.add(new FileInputStream("data2.dat"));
v.add(new FileInputStream("data3.dat"));
Enumeration e = v.elements();
SequenceInputStream s = new SequenceInputStream(e);
The PushbackInputStream
class is a FilterInputStream
that allows data to be pushed back into the input stream and reread by the next
read operation. This functionality is useful for implementing things like
parsers that need to read data and then return it to the input stream. The Java
1.0 version of PushbackInputStream
supported only a one-byte pushback buffer; in Java 1.1 this class has been
enhanced to support a larger pushback buffer.
To create a PushbackInputStream,
pass an InputStream
to its constructor like this:
FileInputStream ef = new FileInputStream("expr.txt");
PushbackInputStream pb = new PushbackInputStream(ef);
This constructor creates a PushbackInputStream that uses a default
one-byte pushback buffer. When you have data that you want to push back into
the input stream to be read by the next read operation, you pass the data to
one of the unread()
methods.
The PushbackReader
class is the character-based equivalent of PushbackInputStream. In the following
example, we create a PushbackReader
with a pushback buffer of 48 characters:
FileReader fileIn = new FileReader("expr.txt");
PushbackReader in = new PushbackReader(fileIn, 48);
Here is an example that shows the use of a PushbackReader:
public String readDigits(PushbackReader pb) {
char c;
StringBuffer buffer = new StringBuffer();
try {
while (true) {
c = (char)pb.read();
if (!Character.isDigit(c))
break;
buffer.append(c);
}
if (c != -1)
pb.unread(c);
}catch (IOException e) {}
return buffer.toString();
}
The above example shows a method that reads characters corresponding to digits from a PushbackReader. When it reads a character that is not a digit, it calls the unread() method so that the nondigit can be read by the next read operation. It then returns a string that contains the digits that were read.
The OutputStream
class is an abstract
class that defines methods to write a stream of bytes sequentially. Java
provides subclasses of the OutputStream
class for writing to files and byte arrays, among other things. Other
subclasses of OutputStream
can be chained together to provide additional logic, such as writing multibyte
data types or converting data to a string representation. It is also easy to
define a subclass of OutputStream
that writes to another kind of destination.
In Java 1.1, the Writer class is an abstract class that defines methods to
write to a stream of characters sequentially. Many of the byte-oriented
subclasses of OutputStream
have counterparts in the character-oriented world of Writer objects.
Thus, there are subclasses of Writer for writing to files and character arrays.
The OutputStream
class is the abstract
superclass of all other byte output stream classes. It defines three write() methods
for writing to a raw stream of bytes:
write(int b)
write(byte[] b)
write(byte[] b, int off, int len)
Some OutputStream
subclasses may implement buffering to increase efficiency. OutputStream
provides a method, flush(),
that tells the OutputStream
to write any buffered output to the underlying device, which may be a disk
drive or a network.
Because the OutputStream
class is abstract,
you cannot create a "pure" OutputStream. However, the various
subclasses of OutputStream
can be used interchangeably. For example, methods often take OutputStream
parameters. This means that such a method accepts any subclass of OutputStream as
an argument.
OutputStream
is designed so that write(byte[])
and write(byte[],
int, int) call write(int).
Thus, when you subclass OutputStream,
you only need to define the write()
method. However, for efficiency's sake, you should also override write(byte[], int, int) with a
method that can write a block of data more efficiently than writing each byte
separately.
The Writer
class is the abstract
parent class of all other character output stream classes. It defines nearly
the same methods as OutputStream,
except that the write()
methods deal with characters instead of bytes:
write(int c)
write(char[] cbuf)
write(char[] cbuf, int off, int len)
write(String str)
write(String str, int off, int len)
Writer
also includes a flush()
method that forces any buffered data to be written to the stream.
Writer
is designed so that write(int)
and write(char[])
both call write(char[],
int, int). Thus, when
you subclass Writer,
you only need to define the write(char[],
int, int) method.
Note that this design is different from, and more efficient than, that of OutputStream.
The OutputStreamWriter
class serves as a bridge between Writer objects and OutputStream objects. Although an OutputStreamWriter
acts like a character stream, it converts its characters to bytes using a
character encoding scheme and writes them to an underlying OutputStream.
This class is the output counterpart of InputStreamReader. When you create an OutputStreamWriter,
specify the underlying OutputStream
and, optionally, the name of an encoding scheme. The following example shows
how to construct an OutputStreamWriter
that writes characters to a file, encoded using the ISO 8859-5 encoding:
String fileName = "encodedfile.txt";
String encodingName = "8859_5";
OutputStreamWriter out;
try {
FileOutputStream fileOut = new FileOutputStream (fileName);
out = new OutputStreamWriter (fileOut, encodingName);
} catch (UnsupportedEncodingException e1) {
System.out.println(encodingName + " is not a supported encoding scheme.");
} catch (IOException e2) {
System.out.println("The file " + fileName + " could not be opened.");
}
The FileOutputStream
class is a subclass of OutputStream
that writes a stream of bytes to a file. The FileOutputStream class has no explicit
open method. Instead, the file is implicitly opened, if appropriate, when you
create the FileOutputStream
object. There are several ways to create a FileOutputStream:
· FileOutputStream f1 = new FileOutputStream("foo.txt");
· FileOutputStream f1 = new FileOutputStream("foo.txt", true);
· File f = new File("foo.txt");
· FileOutputStream f2 = new FileOutputStream(f);
· RandomAccessFile raf;
· raf = new RandomAccessFile("z.txt","rw");
· FileInputStream f3 = new FileOutputStream(raf.getFD());
The FileWriter class is a subclass of Writer that
writes a stream of characters to a file. The characters to be written are
converted to bytes using the default character encoding scheme. If you do not
want to use the default encoding scheme, you need to wrap an OutputStreamWriter
around a FileOutputStream
as shown above. You can create a FileWriter from a filename, a File object, or
a FileDescriptor
object, as described above for FileOutputStream.
The StringWriter
class is a subclass of Writer
that stores its data in a String
object. Internally, it uses a StringBuffer, which can be examined using getBuffer(). A String
containing the data that has been written can be obtained with toString(). The
following example creates a StringWriter
and writes data into it:
StringWriter out = new StringWriter();
char[] buffer = {'b', 'o', 'o', '!', 'h', 'a'};
out.write('B');
out.write("uga");
out.write(buffer, 0, 4);
System.out.println(out.toString());
This example produces the following output:
Bugaboo!
The CharArrayWriter
class is a subclass of Writer
that writes characters to an internal array. There are three ways to retrieve the
data that has been written to the CharArrayWriter:
This example demonstrates how to create a CharArrayWriter,
write data into it, and retrieve the data:
CharArrayWriter out = new CharArrayWriter();
try {
out.write("Daphne");
}catch (IOException e) {}
char[] buffer = out.toCharArray();
System.out.println(buffer);
String result = out.toString();
System.out.println(result);
This example produces the following output:
Daphne
Daphne
The internal buffer of the CharArrayWriter is expanded as needed
when data is written. If you know how many characters you will be writing, you
can make your CharArrayWriter
a little more efficient by passing an initial size to its constructor.
ByteArrayOutputStream
is the byte-oriented equivalent of CharArrayWriter. It works in much the
same way, with the following exceptions:
[1] In Java 1.1,
the default encoding scheme is used for the conversion. In earlier versions,
characters are simply created using the eight bits of each byte as the low
eight bits of the character.
The PipedOuputStream
class is a subclass of OutputStream
that facilitates communication between threads. A PipedOutputStream must be
connected to a PipedInputStream
to be useful, as it writes bytes that can be read by a connected PipedInputStream.
There are a few ways to connect a PipedOutputStream to a PipedInputStream. You can first
create the PipedInputStream
and pass it to the PipedOutputStream
constructor like this:
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream(pi);
You can also create the PipedOutputStream first and pass it to
the PipedInputStream
constructor like this:
PipedOutputStream po = new PipedOutputStream();
PipedInputStream pi = new PipedInputStream(po);
The PipedOutputStream
and PipedInputStream
classes each have a connect()
method you can use to explicitly connect a PipedOutputStream and a PipedInputStream
as follows:
PipedOutputStream po = new PipedOutputStream();
PipedInputStream pi = new PipedInputStream();
po.connect(pi);
Or you can use connect() as follows:
PipedOutputStream po = new PipedOutputStream();
PipedInputStream pi = new PipedInputStream();
pi.connect(po);
Only one PipedInputStream
can be connected to a PipedOutputStream
at a time. If you use a connect()
method to connect a PipedOutputStream
to an already connected PipedInputStream,
any unread bytes from the previously connected PipedOutputStream are lost.
PipedWriter
is the character-based equivalent of PipedOutputStream. It works in the same
way, except that a PipedWriter
is connected to a PipedReader
to complete the pipe, using either the appropriate constructor or the connect()
method.
The FilterOutputStream
class is a wrapper class for OutputStream objects. Conceptually, objects that
belong to a subclass of FilterOutputStream
are wrapped around another OutputStream
object. The constructor for this class requires an OutputStream. The constructor
sets the object's out
instance variable to reference the specified OutputStream, so from that point on, the
FilterOutputStream
is associated with the given OutputStream. All of the methods of FilterOutputStream
work by calling the corresponding methods in the underlying OutputStream.
Because the close()
method of a FilterOutputStream
calls the close()
method of the OutputStream
that it wraps, you do not need to explicitly close the underlying OutputStream.
A FilterOutputStream
does not add any functionality to the object that it wraps, so by itself it is
not very useful. However, subclasses of the FilterOutputStream class do add
functionality to the objects that they wrap in two ways:
The FilterWriter
class is the character-based equivalent of FilterOutputStream. A FilterWriter is
wrapped around an underlying Writer object; the methods of FilterWriter call
the corresponding methods of the underlying Writer. However, unlike FilterOutputStream,
FilterWriter
is an abstract
class, so you cannot instantiate it directly.
The DataOutputStream
class is a subclass of the FilterOutputStream
class that provides methods for writing a variety of data types to an OutputStream.
The DataOutputStream
class implements the DataOutput
interface, so it defines methods for writing all of the primitive Java data
types.
You create a DataOutputStream
by passing a reference to an underlying OutputStream to the constructor. Here is
an example that creates a DataOutputStream
and uses it to write the length of an array as an int and then to write the values
in array as long
values:
void writeLongArray(OutputStream out, long[] a) throws IOException {
DataOutputStream dout = new DataOutputStream(out);
dout.writeInt(a.length);
for (int i = 0; i < a.length; i++) {
dout.writeLong(a[i]);
}
}
The BufferedWriter
class is a subclass of Writer
that stores output destined for an underlying Writer in an internal buffer. When the
buffer fills up, the entire buffer is written, or flushed, to the underlying Writer. Using a BufferedWriter
is usually faster than using a regular Writer because it reduces the number of
calls that must be made to the underlying device, be it a disk or a network.
You can use the flush()
method to force a BufferedWriter
to write the contents of the buffer to the underlying Writer.
The following example shows how to create a BufferedWriter
around a network socket's output stream:
public Writer getBufferedWriter(Socket s) throws IOException {
OutputStreamWriter converter = new OutputStreamWriter(s.getOutputStream());
return new BufferedWriter(converter);
}
First, create an OutputStreamWriter that converts characters to bytes
using the default encoding scheme. After they are converted, the bytes are
written to the socket. Then simply wrap a BufferedWriter around the OutputStreamWriter
to buffer the output.
The BufferedOutputStream
class is the byte-based equivalent of BufferedWriter. It works in the same way
as BufferedWriter,
except that it buffers output for an underlying OutputStream. Here's how you
would rewrite the previous example to create a BufferedOutputStream around a socket:
public OutputStream getBufferedOutputStream(Socket s) throws IOException {
return new BufferedOutputStream(s.getOutputStream());
}
The PrintWriter
class is a subclass of Writer
that provides a set of methods for printing string representations of every
Java data type. A PrintWriter
can be wrapped around an underlying Writer object or an underlying OutputStream
object. In the case of wrapping an OutputStream, any characters written to
the PrintWriter
are converted to bytes using the default encoding scheme.[2] Additional
constructors allow you to specify if the underlying stream should be flushed
after every line-separator character is written.
[2] You can achieve
the same effect using an OutputStreamWriter,
but it is easier to use the PrintWriter(OutputStream)
constructor. However, if you want to use an encoding scheme other than the
default one, you need to create your own OutputStreamWriter.
The PrintWriter
class provides a print()
and a println()
method for every primitive Java data type. As their names imply, the println()
methods do the same thing as their print() counterparts, but also append a
line separator character.
The following example demonstrates how to wrap a PrintWriter
around an OutputStream:
boolean b = true;
char c = '%'
double d = 8.31451
int i = 42;
String s = "R = ";
PrintWriter out = new PrintWriter(System.out, true);
out.print(s);
out.print(d);
out.println();
out.println(b);
out.println(c);
out.println(i);
This example produces the following output:
R = 8.31451
true
%
42
PrintWriter
objects are often used to report errors. For this reason, the methods of this
class do not throw exceptions. Instead, the methods catch any exceptions thrown
by any downstream OutputStream
or Writer
objects and set an internal flag, so that the object can remember that a
problem occurred. You can query the internal flag by calling the checkError() method.
Although you can create a PrintWriter that flushes the underlying
stream every time a line-separator character is written, this may not always be
exactly what you want. Suppose that you are writing a program that has a
character-based user interface, and that you want the program to output a
prompt and then allow the user to input a response on the same line. In order
to make this work with a PrintWriter,
you need to get the PrintWriter
to write the characters in its buffer without writing a line separator. You can
do this by calling the flush()
method.
PrintWriter
is new as of Java 1.1; it is more capable than the PrintStream class. You should use
PrintWriter
instead of PrintStream
because it uses the default encoding scheme to convert characters to bytes for
an underlying OutputStream.
The constructors for PrintStream
are deprecated in Java 1.1. In fact, the whole class probably would have been
deprecated, except that it would have generated a lot of compilation warnings
for code that uses System.out
and System.err.
While streams are used to handle most types of I/O in Java, there
are some nonstream-oriented classes in java.io that are provided for file
manipulation. Namely, the File
class represents a file on the local filesystem, while the RandomAccessFile
class provides nonsequential access to data in a file. In addition, the FilenameFilter
interface can be used to filter a list of filenames.
The File
class represents a file on the local filesystem. You can use an instance of the
File
class to identify a file, obtain information about the file, and even change
information about the file. The easiest way to create a File is to pass
a filename to the File
constructor, like this:
new File("readme.txt")
Although the methods that the File class provides for manipulating
file information are relatively platform independent, filenames must follow the
rules of the local filesystem. The File class does provide some information
that can be helpful in interpreting filenames and path specifications. The
variable separatorChar
specifies the system-specific character used to separate the name of a
directory from what follows.[3] In a Windows environment, this is a backslash (\), while in a
UNIX or Macintosh environment it is a forward slash (/). You can
create a File
object that refers to a file called readme.txt in a directory called myDir as
follows:
[3] This
information is also available as System.getProperty('file.separator'), which is how
the File
class gets it.
new File("myDir" + File.separatorChar + "readme.txt")
The File
class also provides some constructors that make this task easier. For example,
there is a File
constructor that takes two strings as arguments: the first string is the name
of a directory and the second string is the name of a file. The following
example does the exact same thing as the previous example:
new File("myDir", "readme.txt")
The File
class has another constructor that allows you to specify the directory of a
file using a File
object instead of a String:
File dir = new File("myDir");
File f = new File(dir, "readme.txt");
Sometimes a program needs to process a list of files that have
been passed to it in a string. For example, such a list of files is passed to
the Java environment by the CLASSPATH
environment variable and can be accessed by the expression:
System.getProperty("java.class.path")
This list contains one or more filenames separated by separator
characters. In a Windows or Macintosh environment, the separator character is a
semicolon (;),
while in a UNIX environment, the separator character is a colon (:). The
system-specific separator character is specified by the pathSeparatorChar
variable. Thus, to turn the value of CLASSPATH into a collection of File objects, we
can write:
StringTokenizer s;
Vector v = new Vector();
s = new StringTokenizer(System.getProperty("java.class.path"),
File.pathSeparator);
while (s.hasMoreTokens())
v.addElement(new File(s.nextToken()));
You can retrieve the pathname of the file represented by a File object with
getPath(),
the filename without any path information with getName(), and the directory name with getParent().
The File
class also defines methods that return information about the actual file
represented by a File
object. Use exists()
to check whether or not the file exists. isDirectory() and isFile() tell
whether the file is a file or a directory. If the file is a directory, you can
use list()
to get an array of filenames for the files in that directory. The canRead() and canWrite()
methods indicate whether or not a program is allowed to read from or write to a
file. You can also retrieve the length of a file with length() and its
last modified date with lastModified().
A few File
methods allow you to change the information about a file. For example, you can
rename a file with rename()
and delete it with delete().
The mkdir()
and mkdirs()
methods provide a way to create directories within the filesystem.
Many of these methods can throw a SecurityException if a program
does not have permission to access the filesystem, or particular files within
it. If a SecurityManager
has been installed, the checkRead()
and checkWrite()
methods of the SecurityManager
verify whether or not the program has permission to access the filesystem.
The purpose of the FilenameFilter interface is to provide a way for an
object to decide which filenames should be included in a list of filenames. A
class that implements the FilenameFilter
interface must define a method called accept(). This method is passed a File object that
identifies a directory and a String that names a file. The accept() method
is expected to return true
if the specified file should be included in the list, or false if the
file should not be included. Here is an example of a simple FilenameFilter
class that only allows files with a specified suffix to be in a list:
import java.io.File;
import java.io.FilenameFilter;
public class SuffixFilter implements FilenameFilter {
private String suffix;
public SuffixFilter(String suffix) {
this.suffix = "." + suffix;
}
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}
A FilenameFilter
object can be passed as a parameter to the list() method of File to filter
the list that it creates. You can also use a FilenameFilter to limit the choices
shown in a FileDialog.
The RandomAccessFile
class provides a way to read from and write to a file in a nonsequential
manner. The RandomAccessFile
class has two constructors that both take two arguments. The first argument
specifies the file to open, either as a String or a File object. The second argument
is a String
that must be either "r"
or "rw".
If the second argument is "r",
the file is opened for reading only. If the argument is "rw",
however, the file is opened for both reading and writing. The close() method
closes the file. Both constructors and all the methods of the RandomAccessFile
class can throw an IOException
if they encounter an error.
The RandomAccessFile
class defines three different read() methods for reading bytes from a file. The RandomAccessFile
class also implements the DataInput
interface, so it provides additional methods for reading from a file. Most of
these additional methods are related to reading Java primitive types in a
machine-independent way. Multibyte quantities are read assuming the most
significant byte is first and the least significant byte is last. All of these
methods handle an attempt to read past the end of file by throwing an EOFException.
The RandomAccessFile
class also defines three different write() methods for writing bytes of
output. The RandomAccessFile
class also implements the DataOutput
interface, so it provides additional methods for writing to a file. Most of
these additional methods are related to writing Java primitive types in a
machine-independent way. Again, multibyte quantities are written with the most
significant byte first and the least significant byte last.
The RandomAccessFile
class would not live up to its name if it did not provide a way to access a
file in a nonsequential manner. The getFilePointer() method returns the
current position in the file, while the seek() method provides a way to set the
position. Finally, the length()
method returns the length of the file in bytes.