Typical uses of IO streams | CodeGuru

Typical uses of IO streams

Bruce Eckel’s Thinking in Java Contents | Prev | Next Although there are a lot of IO stream classes in the library that can be combined in many different ways, there are just a few ways that you’ll probably end up using them. However, they require attention to get the correct combinations. The following rather […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
8 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Although


there are a lot of IO stream classes in the library that can be combined in


many different ways, there are just a few ways that you’ll probably end


up using them. However, they require attention to get the correct combinations.


The following rather long example shows the creation and use of

typical
IO configurations so you can use it as a reference when writing your own code.
Note that each configuration begins with a commented number and title that
corresponds to the heading for the appropriate explanation that follows in the
text.
//: IOStreamDemo.java
// Typical IO Stream Configurations
import java.io.*;
import com.bruceeckel.tools.*;
 
public class IOStreamDemo {
  public static void main(String[] args) {
    try {
      // 1. Buffered input file
      DataInputStream in =
        new DataInputStream(
          new BufferedInputStream(
            new FileInputStream(args[0])));
      String s, s2 = new String();
      while((s = in.readLine())!= null)
        s2 += s + "n";
      in.close();
 
      // 2. Input from memory
      StringBufferInputStream in2 =
          new StringBufferInputStream(s2);
      int c;
      while((c = in2.read()) != -1)
        System.out.print((char)c);
 
      // 3. Formatted memory input
      try {
        DataInputStream in3 =
          new DataInputStream(
            new StringBufferInputStream(s2));
        while(true)
          System.out.print((char)in3.readByte());
      } catch(EOFException e) {
        System.out.println(
          "End of stream encountered");
      }
 
      // 4. Line numbering & file output
      try {
        LineNumberInputStream li =
          new LineNumberInputStream(
            new StringBufferInputStream(s2));
        DataInputStream in4 =
          new DataInputStream(li);
        PrintStream out1 =
          new PrintStream(
            new BufferedOutputStream(
              new FileOutputStream(
                "IODemo.out")));
        while((s = in4.readLine()) != null )
          out1.println(
            "Line " + li.getLineNumber() + s);
        out1.close(); // finalize() not reliable!
      } catch(EOFException e) {
        System.out.println(
          "End of stream encountered");
      }
 
      // 5. Storing & recovering data
      try {
        DataOutputStream out2 =
          new DataOutputStream(
            new BufferedOutputStream(
              new FileOutputStream("Data.txt")));
        out2.writeBytes(
          "Here's the value of pi: n");
        out2.writeDouble(3.14159);
        out2.close();
        DataInputStream in5 =
          new DataInputStream(
            new BufferedInputStream(
              new FileInputStream("Data.txt")));
        System.out.println(in5.readLine());
        System.out.println(in5.readDouble());
      } catch(EOFException e) {
        System.out.println(
          "End of stream encountered");
      }
 
      // 6. Reading/writing random access files
      RandomAccessFile rf =
        new RandomAccessFile("rtest.dat", "rw");
      for(int i = 0; i < 10; i++)
        rf.writeDouble(i*1.414);
      rf.close();
 
      rf =
        new RandomAccessFile("rtest.dat", "rw");
      rf.seek(5*8);
      rf.writeDouble(47.0001);
      rf.close();
 
      rf =
        new RandomAccessFile("rtest.dat", "r");
      for(int i = 0; i < 10; i++)
        System.out.println(
          "Value " + i + ": " +
          rf.readDouble());
      rf.close();
 
      // 7. File input shorthand
      InFile in6 = new InFile(args[0]);
      String s3 = new String();
      System.out.println(
        "First line in file: " +
        in6.readLine());
        in6.close();
 
      // 8. Formatted file output shorthand
      PrintFile out3 = new PrintFile("Data2.txt");
      out3.print("Test of PrintFile");
      out3.close();
 
      // 9. Data file output shorthand
      OutFile out4 = new OutFile("Data3.txt");
      out4.writeBytes("Test of outDataFilenr");
      out4.writeChars("Test of outDataFilenr");
      out4.close();
 
    } catch(FileNotFoundException e) {
      System.out.println(
        "File Not Found:" + args[0]);
    } catch(IOException e) {
      System.out.println("IO Exception");
    }
  }
} ///:~ 

Input
streams

Of


course, one common thing you’ll want to do is print formatted output to


the console, but that’s already been simplified in the package


com.bruceeckel.tools

created in Chapter 5.

Parts


1 through 4 demonstrate the creation and use of input streams (although part 4


also shows the simple use of an output stream as a testing tool).


1.
Buffered input file

To


open a file for input, you use a

FileInputStream
with a
String
or a
File
object as the file name. For speed, you’ll want that file to be buffered
so you give the resulting handle to the constructor for a
BufferedInputStream.
To read input in a formatted fashion, you give that resulting handle to the
constructor for a
DataInputStream,
which is your final object and the interface you read from.

In


this example, only the

readLine( )
method is used, but of course any of the
DataInputStream
methods are available. When you reach the end of the file,
readLine( )
returns
null
so that is used to break out of the
while
loop.

The


String
s2

is used to accumulate the entire contents of the file (including newlines that


must be added since


readLine( )

strips them off).


s2

is


then used in the later portions of this program. Finally,


close( )

is called to close the file. Technically,


close( )

will be called when


finalize( )

is run, and this is supposed to happen (whether or not garbage collection


occurs) as the program exits. However, Java 1.0


has a rather important bug, so this doesn’t happen. In Java 1.1

you must explicitly call
System.runFinalizersOnExit(true)
to guarantee that
finalize( )
will be called for every object in the system. The safest approach is to
explicitly call
close( )
for files.


2.
Input from memory

This


piece takes the


String
s2

that now contains the entire contents of the file and uses it to create a

StringBufferInputStream.
(A
String,
not a
StringBuffer,
is required as the constructor argument.) Then
read( )
is used to read each character one at a time and send it out to the console.
Note that
read( )
returns the next byte as an
int
and thus it must be cast to a
char
to print properly.


3.
Formatted memory input

The


interface for


StringBufferInputStream

is limited, so you usually enhance it by wrapping it inside a

DataInputStream.
However, if you choose to read the characters out a byte at a time using
readByte( ),
any value is valid so the return value cannot be used to detect the end of
input. Instead, you can use the
available( )
method
to find out how many more characters are available. Here’s an example
that shows how to read a file one byte at a time:
//: TestEOF.java
// Testing for the end of file while reading
// a byte at a time.
import java.io.*;
 
public class TestEOF {
  public static void main(String[] args) {
    try {
      DataInputStream in =
        new DataInputStream(
         new BufferedInputStream(
          new FileInputStream("TestEof.java")));
      while(in.available() != 0)
        System.out.print((char)in.readByte());
    } catch (IOException e) {
      System.err.println("IOException");
    }
  }
} ///:~ 

Note


that


available( )

works differently depending on what sort of medium you’re reading from


– it’s literally “the number of bytes that can be read


without
blocking

.”


With a file this means the whole file, but with a different kind of stream this
might not be true, so use it thoughtfully.

You


could also detect the end of input in cases like these by catching an


exception. However, the use of exceptions for control flow is considered a


misuse of that feature.


4.
Line numbering and file output

This


example shows the use of the

LineNumberInputStream
to keep track of the input line numbers. Here, you cannot simply gang all the
constructors together, since you have to keep a handle to the
LineNumberInputStream.
(Note that this is
not
an inheritance situation, so you cannot simply cast
in4
to a
LineNumberInputStream.)
Thus,
li
holds the handle to the
LineNumberInputStream,
which is then used to create a
DataInputStream
for easy reading.

This


example also shows how to write formatted data to a file. First, a

FileOutputStream
is created to connect to the file. For efficiency, this is made a
BufferedOutputStream,
which is what you’ll virtually always want to do, but you’re forced
to do it explicitly. Then for the formatting it’s turned into a
PrintStream.
The data file created this way is readable as an ordinary text file.

One


of the methods that indicates when a

DataInputStream
is exhausted is
readLine( ),
which returns
null
when there are no more strings to read. Each line is printed to the file along
with its line number, which is acquired through
li.

You’ll


see an explicit


close( )

for


out1

,


which would make sense


if

the program were to turn around and read the same file again. However, this


program ends without ever looking at the file


IODemo.out

.


As mentioned before, if you don’t call


close( )

for all your output files, you might discover that the buffers don’t get


flushed so they’re incomplete.


Output
streams

The


two primary kinds of output streams are separated by the way they write data:


one writes it for human consumption, and the other writes it to be re-acquired


by a

DataInputStream.
The
RandomAccessFile
stands alone, although its data format is compatible with the
DataInputStream
and
DataOutputStream.


5.
Storing and recovering data

A


PrintStream
formats data so it’s readable by a human. To output data so that it can
be recovered by another stream, you use a
DataOutputStream
to write the data and a
DataInputStream
to recover the data. Of course, these streams could be anything, but here a
file is used, buffered for both reading and writing.

Note


that the character string is written using

writeBytes( )
and not
writeChars( ).
If you use the latter, you’ll be writing the 16-bit Unicode characters.
Since there is no complementary “readChars” method in
DataInputStream,
you’re stuck pulling these characters off one at a time with
readChar( ).
So for ASCII, it’s easier to write the characters as bytes followed by a
newline; then use
readLine( )
to read back the bytes as a regular ASCII line.

The


writeDouble( )
stores the
double
number to the stream and the complementary
readDouble( )
recovers it. But for any of the reading methods to work correctly, you must
know the exact placement of the data item in the stream, since it would be
equally possible to read the stored
double
as a simple sequence of bytes, or as a
char,
etc. So you must either have a fixed format for the data in the file or extra
information must be stored in the file that you parse to determine where the
data is located.


6.
Reading and writing random access files

As


previously noted, the


RandomAccessFile

is almost totally isolated from the rest of the IO hierarchy, save for the fact


that it implements the


DataInput

and


DataOutput

interfaces. So you cannot combine it with any of the aspects of the


InputStream

and


OutputStream

subclasses. Even though it might make sense to treat a


ByteArrayInputStream

as a random access element, you can use


RandomAccessFile

to only open a file. You must assume a


RandomAccessFile

is properly buffered since you cannot add that.

The


one option you have is in the second constructor argument: you can open a


RandomAccessFile

to read (


“r”

)


or read and write (


“rw”

).

Using


a


RandomAccessFile

is like using a combined


DataInputStream

and


DataOutputStream

(because it implements the equivalent interfaces). In addition, you can see that

seek( )
is used to move about in the file and change one of the values.

Advertisement

Shorthand
for file manipulation

Since


there are certain canonical forms that you’ll be using regularly with


files, you may wonder why you have to do all of that typing – this is one


of the drawbacks of the decorator pattern. This portion shows the creation and


use of shorthand versions of typical file reading and writing configurations.


These shorthands are placed in the


package
com.bruceeckel.tools

that was begun in Chapter 5 (See page


196

).


To add each class to the library, simply place it in the appropriate directory


and add the


package

statement.


7.
File input shorthand

The


creation of an object that reads a file from a buffered


DataInputStream

can be encapsulated into a class called


InFile

:

//: InFile.java
// Shorthand class for opening an input file
package com.bruceeckel.tools;
import java.io.*;
 
public class InFile extends DataInputStream {
  public InFile(String filename)
    throws FileNotFoundException {
    super(
      new BufferedInputStream(
        new FileInputStream(filename)));
  }
  public InFile(File file)
    throws FileNotFoundException {
    this(file.getPath());
  }
} ///:~ 

Both


the


String

versions of the constructor and the


File

versions are included, to parallel the creation of a


FileInputStream

.

Now


you can reduce your chances of repetitive stress syndrome while creating files,


as seen in the example.


8.
Formatted file output shorthand

The


same kind of approach can be taken to create a


PrintStream

that writes to a buffered file. Here’s the extension to


com.bruceeckel.tools

:

//: PrintFile.java
// Shorthand class for opening an output file
// for human-readable output.
package com.bruceeckel.tools;
import java.io.*;
 
public class PrintFile extends PrintStream {
  public PrintFile(String filename)
    throws IOException {
    super(
      new BufferedOutputStream(
        new FileOutputStream(filename)));
  }
  public PrintFile(File file)
    throws IOException {
    this(file.getPath());
  }
} ///:~ 

Note


that it is not possible for a constructor to catch an exception that’s


thrown by a base-class constructor.


9.
Data file output shorthand

Finally,


the same kind of shorthand can create a buffered output file for data storage


(as opposed to human-readable storage):

//: OutFile.java
// Shorthand class for opening an output file
// for data storage.
package com.bruceeckel.tools;
import java.io.*;
 
public class OutFile extends DataOutputStream {
  public OutFile(String filename)
    throws IOException {
    super(
      new BufferedOutputStream(
        new FileOutputStream(filename)));
  }
  public OutFile(File file)
    throws IOException {
    this(file.getPath());
  }
} ///:~ 

It


is curious (and unfortunate) that the Java library designers didn’t think


to provide these conveniences as part of their standard.


Reading
from standard input

Following


the approach pioneered in Unix of “standard input,” “standard


output,” and “standard error output,” Java has

System.in,
System.out,
and
System.err.
Throughout the book you’ve seen how to write to standard output using
System.out,
which is already pre-wrapped as a
PrintStream
object.
System.err
is likewise a
PrintStream,
but
System.in
is a raw
InputStream,
with no wrapping. This means that while you can use
System.out
and
System.err
right away,
System.in
must be wrapped before you can read from it.

Typically,


you’ll want to read input a line at a time using


readLine( )

,


so you’ll want to wrap


System.in

in a


DataInputStream

.


This is the “old” Java 1.0


way to do line input. A bit later in the chapter you’ll see the Java 1.1

solution. Here’s an example that simply echoes each line that you type in:
//: Echo.java
// How to read from standard input
import java.io.*;
 
public class Echo {
  public static void main(String[] args) {
    DataInputStream in =
      new DataInputStream(
        new BufferedInputStream(System.in));
    String s;
    try {
      while((s = in.readLine()).length() != 0)
        System.out.println(s);
      // An empty line terminates the program
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
} ///:~ 

The


reason for the


try

block is that

readLine( )
can throw an
IOException.
Note that
System.in
should also be buffered, as with most streams

It’s


a bit inconvenient that you’re forced to wrap


System.in

in a


DataInputStream

in each program, but perhaps it was designed this way to allow maximum


flexibility.


Advertisement

Piped
streams

The


PipedInputStream
and
PipedOutputStream
have been mentioned only briefly in this chapter. This is not to suggest that
they aren’t useful, but their value is not apparent until you begin to
understand multithreading, since the piped streams are used to communicate
between threads. This is covered along with an example in Chapter 14.
Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.