Input and Output – Java I/O: Part I

20.1 Input and Output

Java provides I/O streams as a general mechanism for dealing with data input and output. I/O streams implement sequential processing of data. An input stream allows an application to read a sequence of data, and an output stream allows an application to write a sequence of data. An input stream acts as a source of data, and an output stream acts as a destination of data. The following entities can act as both input and output streams:

  • A file—which is the focus in this chapter
  • An array of bytes or characters
  • A network connection

There are two categories of I/O streams:

  • Byte streams that process bytes as a unit of data
  • Character streams that process characters as a unit of data

A low-level I/O stream operates directly on the data source (e.g., a file or an array of bytes), and processes the data primarily as bytes.

A high-level I/O stream is chained to an underlying stream, and provides additional capabilities for processing data managed by the underlying stream—for example, processing bytes from the underlying stream as Java primitive values or objects. In other words, a high-level I/O stream acts as a wrapper for the underlying stream.

In the rest of this chapter we primarily explore how to use I/O streams of the standard I/O API provided by the java.io package to read and write various kinds of data that is stored in files.

20.2 Byte Streams: Input Streams and Output Streams

The abstract classes InputStream and OutputStream in the java.io package are the root of the inheritance hierarchies for handling the reading and writing of data as bytes (Figure 20.1). Their subclasses, implementing different kinds of input and output (I/O) streams, override the following methods from the InputStream and OutputStream classes to customize the reading and writing of bytes, respectively:

Figure 20.1 Partial Byte Stream Inheritance Hierarchies in the java.io Package

From the InputStream abstract class:

Click here to view code image

int read() throws IOException
int read(byte[] b) throws IOException
int read(byte[] b, int off, int len) throws IOException

Note that the first read() method reads a byte, but returns an int value. The byte read resides in the eight least significant bits of the int value, while the remaining bits in the int value are zeroed out. The read() methods return the value –1 when the end of the input stream is reached.

Click here to view code image

long transferTo(OutputStream out) throws IOException

Reads bytes from this input stream and writes to the specified output stream in the order they are read. The I/O streams are not closed after the operation (see below).

From the OutputStream abstract class:

Click here to view code image

void write(int b) throws IOException
void write(byte[] b) throws IOException
void write(byte[] b, int off, int len) throws IOException

The first write() method takes an int as an argument, but truncates it to the eight least significant bits before writing it out as a byte to the output stream.

Click here to view code image

void close() throws IOException       
Both
 InputStream
and
 OutputStream
void flush() throws IOException       
Only for
 OutputStream

A I/O stream should be closed when no longer needed, to free system resources. Closing an output stream automatically flushes the output stream, meaning that any data in its internal buffer is written out.

Since byte streams also implement the AutoCloseable interface, they can be declared in a try-with-resources statement (§7.7, p. 407) that will ensure they are properly closed after use at runtime.

An output stream can also be manually flushed by calling the second method.

Read and write operations on streams are blocking operations—that is, a call to a read or write method does not return before a byte has been read or written.

Many methods in the classes contained in the java.io package throw the checked IOException. A calling method must therefore either catch the exception explicitly, or specify it in a throws clause.

Table 20.1 and Table 20.2 give an overview of selected byte streams. Usually an output stream has a corresponding input stream of the same type.

Table 20.1 Selected Input Streams

FileInputStreamData is read as bytes from a file. The file acting as the input stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterInputStreamThe superclass of all input filter streams. An input filter stream must be chained to an underlying input stream.
DataInputStreamA filter stream that allows the binary representation of Java primitive values to be read from an underlying input stream. The underlying input stream must be specified.
ObjectInputStreamA filter stream that allows binary representations of Java objects and Java primitive values to be read from a specified input stream.

Table 20.2 Selected Output Streams

FileOutputStreamData is written as bytes to a file. The file acting as the output stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterOutputStreamThe superclass of all output filter streams. An output filter stream must be chained to an underlying output stream.
PrintStreamA filter output stream that converts a text representation of Java objects and Java primitive values to bytes before writing them to an underlying output stream, which must be specified. This is the type of System.out and System.err (p. 1255). However, the PrintWriter class is recommended when writing characters rather than bytes (p. 1247).
DataOutputStreamA filter stream that allows the binary representation of Java primitive values to be written to an underlying output stream. The underlying output stream must be specified.
ObjectOutputStreamA filter stream that allows the binary representation of Java objects and Java primitive values to be written to a specified underlying output stream.

I/O Filter Streams – Java I/O: Part I

I/O Filter Streams

An I/O filter stream is a high-level I/O stream that provides additional functionality to an underlying stream to which it is chained. The data from the underlying stream is manipulated in some way by the filter stream. The FilterInputStream and FilterOutputStream classes, together with their subclasses, define input and output filter streams. The subclasses BufferedInputStream and BufferedOutputStream implement filter streams that buffer input from and output to the underlying stream, respectively. The subclasses DataInputStream and DataOutputStream implement filter streams that allow binary representation of Java primitive values to be read and written, respectively, from and to an underlying stream.

Reading and Writing Binary Values

The java.io package contains the two interfaces DataInput and DataOutput, which streams can implement to allow reading and writing of binary representation of Java primitive values (boolean, char, byte, short, int, long, float, double). The methods for writing binary representations of Java primitive values are named writeX, where X is any Java primitive data type. The methods for reading binary representations of Java primitive values are similarly named readX. Table 20.3 gives an overview of the readX() and writeX() methods found in these two interfaces. A file containing binary values (i.e., binary representation of Java primitive values) is usually called a binary file.

Table 20.3 The DataInput and DataOutput Interfaces

TypeMethods in the DataInput interfaceMethods in the DataOutput interface
booleanreadBoolean()writeBoolean(boolean b)
charreadChar()writeChar(int c)
bytereadByte()writeByte(int b)
shortreadShort()writeShort(int s)
intreadInt()writeInt(int i)
longreadLong()writeLong(long l)
floatreadFloat()writeFloat(float f)
doublereadDouble()writeDouble(double d)
StringreadLine()writeChars(String str)
StringreadUTF()writeUTF(String str)

Note the methods provided for reading and writing strings. However, the recommended practice for reading and writing characters is to use character streams, called readers and writers, which are discussed in §20.3.

The filter streams DataOutputStream and DataInputStream implement the DataOutput and DataInput interfaces, respectively, and can be used to read and write binary representation of Java primitive values from and to an underlying stream. Both the writeX() and readX() methods throw an IOException in the event of an I/O error. In particular, the readX() methods throw an EOFException (a subclass of IOException) if the input stream does not contain the correct number of bytes to read. Bytes can also be skipped from a DataInput stream, using the skipBytes(int n) method which skips n bytes.

Click here to view code image

DataInputStream(InputStream in)
DataOutputStream(OutputStream out)

These constructors can be used to set up filter streams from an underlying stream for reading and writing Java primitive values, respectively.

File Streams – Java I/O: Part I

File Streams

The subclasses FileInputStream and FileOutputStream represent low-level streams that define byte input and output streams that are connected to files. Data can only be read or written as a sequence of bytes. Such file streams are typically used for handling image data.

A FileInputStream for reading bytes can be created using the following constructor:

Click here to view code image

FileInputStream(String name) throws FileNotFoundException

The file designated by the file name is assigned to a new file input stream.

If the file does not exist, a FileNotFoundException is thrown. If it exists, it is set to be read from the beginning. A SecurityException is thrown if the file does not have read access.

A FileOutputStream for writing bytes can be created using the following constructor:

Click here to view code image

FileOutputStream(String name) throws FileNotFoundException
FileOutputStream(String name, boolean append) throws FileNotFoundException

The file designated by the file name is assigned to a new file output stream.

If the file does not exist, it is created. If it exists, its contents are reset, unless the appropriate constructor is used to indicate that output should be appended to the file. A SecurityException is thrown if the file does not have write access or it cannot be created. A FileNotFoundException is thrown if it is not possible to open the file for any other reasons.

The FileInputStream class provides an implementation for the read() methods in its superclass InputStream. Similarly, the FileOutputStream class provides an implementation for the write() methods in its superclass OutputStream.

Example 20.1 demonstrates using a buffer to read bytes from and write bytes to file streams. The input and the output file names are specified on the command line. The streams are created at (1) and (2).

The bytes are read into a buffer by the read() method that returns the number of bytes read. The same number of bytes from the buffer are written to the output file by the write() method, regardless of whether the buffer is full or not after every read operation.

The end of file is reached when the read() method returns the value -1. The code at (3a) using a buffer can be replaced by a call to the transferTo() method at (3b) to do the same operation. The streams are closed by the try-with-resources statement. Note that most of the code consists of a try-with-resources statement with catch clauses to handle the various exceptions.

Example 20.1 Copying a File Using a Byte Buffer

Click here to view code image

/* Copy a file using a byte buffer.
   Command syntax: java CopyFile <from_file> <to_file> */
import java.io.*;
class CopyFile {
  public static void main(String[] args) {
    try (// Assign the files:
        FileInputStream fromFile = new FileInputStream(args[0]);       // (1)
        FileOutputStream toFile = new FileOutputStream(args[1]))  {    // (2)
      // Copy bytes using buffer:                                      // (3a)
      byte[] buffer = new byte[1024];
      int length = 0;
      while((length = fromFile.read(buffer)) != -1) {
        toFile.write(buffer, 0, length);
      }
      // Transfer bytes:
//    fromFile.transferTo(toFile);                                     // (3b)

    } catch(ArrayIndexOutOfBoundsException e) {
      System.err.println(“Usage: java CopyFile <from_file> <to_file>”);
    } catch(FileNotFoundException e) {
      System.err.println(“File could not be copied: ” + e);
    } catch(IOException e) {
      System.err.println(“I/O error.”);
    }
  }
}

Writing Binary Values to a File – Java I/O: Part I

Writing Binary Values to a File

To write the binary representation of Java primitive values to a binary file, the following procedure can be used, which is also depicted in Figure 20.2.

Figure 20.2 Stream Chaining for Reading and Writing Binary Values to a File

Use a try-with-resources statement for declaring and creating the necessary streams, which guarantees closing of the filter stream and any underlying stream.

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“primitives.data”);

Create a DataOutputStream which is chained to the FileOutputStream:

Click here to view code image

DataOutputStream outputStream = new DataOutputStream(outputFile);

Write Java primitive values using relevant writeX() methods:

Note that in the case of char, byte, and short data types, the int argument to the writeX() method is converted to the corresponding type, before it is written (see Table 20.3).

See also the numbered lines in Example 20.2 corresponding to the steps above.

Reading Binary Values from a File

To read the binary representation of Java primitive values from a binary file, the following procedure can be used, which is also depicted in Figure 20.2.

Use a try-with-resources statement for declaring and creating the necessary streams, which guarantees closing of the filter stream and any underlying stream.

Create a FileInputStream:

Click here to view code image

FileInputStream inputFile = new FileInputStream(“primitives.data”);

Create a DataInputStream which is chained to the FileInputStream:

Click here to view code image

DataInputStream inputStream = new DataInputStream(inputFile);

Read the (exact number of) Java primitive values in the same order they were written out to the file, using relevant readX() methods. Not doing so will unleash the wrath of the IOException.

See also the numbered lines in Example 20.2 corresponding to the steps above. Example 20.2 uses both procedures described above: first to write and then to read some Java primitive values to and from a file. It also checks to see if the end of the stream has been reached, signaled by an EOFException. The values are also written to the standard output stream.

Example 20.2 Reading and Writing Binary Values

Click here to view code image

import java.io.*;
public class BinaryValuesIO {
  public static void main(String[] args) throws IOException {
    // Write binary values to a file:
    try(                                                                   // (1)
        // Create a FileOutputStream.                                         (2)
        FileOutputStream outputFile = new FileOutputStream(“primitives.data”);
        // Create a DataOutputStream which is chained to the FileOutputStream.(3)
        DataOutputStream outputStream = new DataOutputStream(outputFile)) {
      // Write Java primitive values in binary representation:                (4)
      outputStream.writeBoolean(true);
      outputStream.writeChar(‘A’);               // int written as Unicode char
      outputStream.writeByte(Byte.MAX_VALUE);    // int written as 8-bits byte
      outputStream.writeShort(Short.MIN_VALUE);  // int written as 16-bits short
      outputStream.writeInt(Integer.MAX_VALUE);
      outputStream.writeLong(Long.MIN_VALUE);
      outputStream.writeFloat(Float.MAX_VALUE);
      outputStream.writeDouble(Math.PI);
    }
    // Read binary values from a file:
    try (                                                                  // (1)
        // Create a FileInputStream.                                          (2)
        FileInputStream inputFile = new FileInputStream(“primitives.data”);
        // Create a DataInputStream which is chained to the FileInputStream.  (3)
        DataInputStream inputStream = new DataInputStream(inputFile)) {
      // Read the binary representation of Java primitive values
      // in the same order they were written out:                             (4)
      System.out.println(inputStream.readBoolean());
      System.out.println(inputStream.readChar());
      System.out.println(inputStream.readByte());
      System.out.println(inputStream.readShort());
      System.out.println(inputStream.readInt());
      System.out.println(inputStream.readLong());
      System.out.println(inputStream.readFloat());
      System.out.println(inputStream.readDouble());
      // Check for end of stream:
      int value = inputStream.readByte();
      System.out.println(“More input: ” + value);
    } catch (FileNotFoundException fnf) {
      System.out.println(“File not found.”);
    } catch (EOFException eof) {
      System.out.println(“End of input stream.”);
    }
  }
}

Output from the program:

true
A
127
-32768
2147483647
-9223372036854775808
3.4028235E38
3.141592653589793
End of input stream.

Character Streams: Readers and Writers – Java I/O: Part I

20.3 Character Streams: Readers and Writers

A character encoding is a scheme for representing characters. Java programs represent values of the char type internally in the 16-bit Unicode character encoding, but the host platform might use another character encoding to represent and store characters externally. For example, the ASCII (American Standard Code for Information Interchange) character encoding is widely used to represent characters on many platforms. However, it is only one small subset of the Unicode standard.

The abstract classes Reader and Writer are the roots of the inheritance hierarchies for streams that read and write Unicode characters using a specific character encoding (Figure 20.3). A reader is an input character stream that implements the Readable interface and reads a sequence of Unicode characters, and a writer is an output character stream that implements the Writer interface and writes a sequence of Unicode characters. Character encodings (usually called charsets) are used by readers and writers to convert between external bytes and internal Unicode characters. The same character encoding that was used to write the characters must be used to read those characters. The java.nio.charset.Charset class represents charsets. Kindly refer to the Charset class API documentation for more details.

Figure 20.3 Selected Character Streams in the java.io Package

Click here to view code image

static Charset forName(String charsetName)

Returns a charset object for the named charset. Selected common charset names are “UTF-8”, “UTF-16”, “US-ASCII”, and “ISO-8859-1”.

Click here to view code image

static Charset defaultCharset()

Returns the default charset of this Java virtual machine.

Table 20.4 and Table 20.5 give an overview of some selected character streams found in the java.io package.

Table 20.4 Selected Readers

ReaderDescription
BufferedReaderA reader is a high-level input stream that buffers the characters read from an underlying stream. The underlying stream must be specified and an optional buffer size can be given.
InputStreamReaderCharacters are read from a byte input stream which must be specified. The default character encoding is used if no character encoding is explicitly specified in the constructor. This class provides the bridge from byte streams to character streams.
FileReaderCharacters are read from a file, using the default character encoding, unless an encoding is explicitly specified in the constructor. The file can be specified by a String file name. It automatically creates a FileInputStream that is associated with the file.

Table 20.5 Selected Writers

WritersDescription
BufferedWriterA writer is a high-level output stream that buffers the characters before writing them to an underlying stream. The underlying stream must be specified, and an optional buffer size can be specified.
OutputStreamWriterCharacters are written to a byte output stream that must be specified. The default character encoding is used if no explicit character encoding is specified in the constructor. This class provides the bridge from character streams to byte streams.
FileWriterCharacters are written to a file, using the default character encoding, unless an encoding is explicitly specified in the constructor. The file can be specified by a String file name. It automatically creates a FileOutputStream that is associated with the file. A boolean parameter can be specified to indicate whether the file should be overwritten or appended with new content.
PrintWriterA print writer is a high-level output stream that allows text representation of Java objects and Java primitive values to be written to an underlying output stream or writer. The underlying output stream or writer must be specified. An explicit encoding can be specified in the constructor, and also whether the print writer should do automatic line flushing.

Readers use the following methods for reading Unicode characters:

Click here to view code image

int read() throws IOException
int read(char cbuf[]) throws IOException
int read(char cbuf[], int off, int len) throws IOException

Note that the read() methods read the character as an int in the range 0 to 65,535 (0x0000–0xFFFF).

The first method returns the character as an int value. The last two methods store the characters in the specified array and return the number of characters read. The value -1 is returned if the end of the stream has been reached.

Click here to view code image

long skip(long n) throws IOException

A reader can skip over characters using the skip() method.

Click here to view code image

void close() throws IOException

Like byte streams, a character stream should be closed when no longer needed in order to free system resources.

Click here to view code image

boolean ready() throws IOException

When called, this method returns true if the next read operation is guaranteed not to block. Returning false does not guarantee that the next read operation will block.

Click here to view code image

long transferTo(Writer out) throws IOException

Reads all characters from this reader and writes the characters to the specified writer in the order they are read. The I/O streams are not closed after the operation.

Writers use the following methods for writing Unicode characters:

Click here to view code image

void write(int c) throws IOException

The write() method takes an int as an argument, but writes only the least significant 16 bits.

Click here to view code image

void write(char[] cbuf) throws IOException
void write(String str) throws IOException
void write(char[] cbuf, int off, int length) throws IOException
void write(String str, int off, int length) throws IOException

Write the characters from an array of characters or a string.

Click here to view code image

void close() throws IOException
void flush() throws IOException

Like byte streams, a character stream should be closed when no longer needed in order to free system resources. Closing a character output stream automatically flushes the stream. A character output stream can also be manually flushed.

Like byte streams, many methods of the character stream classes throw a checked IOException that a calling method must either catch explicitly or specify in a throws clause. They also implement the AutoCloseable interface, and can thus be declared in a try-with-resources statement (§7.7, p. 407) that will ensure they are automatically closed after use at runtime.

Analogous to Example 20.1 that demonstrates usage of a byte buffer for writing and reading bytes to and from file streams, Example 20.3 demonstrates using a character buffer for writing and reading characters to and from file streams. Later in this section, we will use buffered readers (p. 1251) and buffered writers (p. 1250) for reading and writing characters from files, respectively.

Example 20.3 Copying a File Using a Character Buffer

Click here to view code image

/* Copy a file using a character buffer.
   Command syntax: java CopyCharacterFile <from_file> <to_file> */
import java.io.*;
class CopyCharacterFile {
  public static void main(String[] args) {
    try (// Assign the files:
        FileReader fromFile = new FileReader(args[0]);                 // (1)
        FileWriter toFile = new FileWriter(args[1]))  {                // (2)
      // Copy characters using buffer:                                 // (3a)
      char[] buffer = new char[1024];
      int length = 0;
      while((length = fromFile.read(buffer)) != -1) {
        toFile.write(buffer, 0, length);
      }
      // Transfer characters:
//    fromFile.transferTo(toFile);                                     // (3b)
    } catch(ArrayIndexOutOfBoundsException e) {
      System.err.println(“Usage: java CopyCharacterFile <from_file> <to_file>”);
    } catch(FileNotFoundException e) {
      System.err.println(“File could not be copied: ” + e);
    } catch(IOException e) {
      System.err.println(“I/O error.”);
    }
  }
}

Print Writers – Java I/O: Part I

Print Writers

The capabilities of the OutputStreamWriter and the InputStreamReader classes are limited, as they primarily write and read characters.

In order to write a text representation of Java primitive values and objects, a Print-Writer should be chained to either a writer, or a byte output stream, or accept a String file name, using one of the following constructors:

Click here to view code image

PrintWriter(Writer out)
PrintWriter(Writer out, boolean autoFlush)
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)
PrintWriter(String fileName) throws FileNotFoundException
PrintWriter(String fileName, Charset charset)
            throws FileNotFoundException
PrintWriter(String fileName, String charsetName)
            throws FileNotFoundException, UnsupportedEncodingException

The boolean autoFlush argument specifies whether the PrintWriter should do automatic line flushing.

When the underlying writer is specified, the character encoding supplied by the underlying writer is used. However, an OutputStream has no notion of any character encoding, so the necessary intermediate OutputStreamWriter is automatically created, which will convert characters into bytes, using the default character encoding.

boolean checkError()
protected void clearError()

The first method flushes the output stream if it’s not closed and checks its error state.

The second method clears the error state of this output stream.

Writing Text Representation of Primitive Values and Objects

In addition to overriding the write() methods from its super class Writer, the Print-Writer class provides methods for writing text representation of Java primitive values and of objects (see Table 20.6). The println() methods write the text representation of their argument to the underlying stream, and then append a line separator. The println() methods use the correct platform-dependent line separator. For example, on Unix-based platforms the line separator is ‘\n’ (newline), while on Windows-based platforms it is “\r\n” (carriage return + newline) and on Mac-based platforms it is ‘\r’ (carriage return).

Table 20.6 Print Methods of the PrintWriter Class

The print() methodsThe println() methods
_
print(boolean b)
print(char c)
print(int i)
print(long l)
print(float f)
print(double d)
print(char[] s)
print(String s)
print(Object obj)

println()
println(boolean b)
println(char c)
println(int i)
println(long l)
println(float f)
println(double d)
println(char[] ca)
println(String str)
println(Object obj)

The print methods create a text representation of an object by calling the toString() method on the object. The print methods do not throw any IOException. Instead, the checkError() method of the PrintWriter class must be called to check for errors.

Reading Text Files – Java I/O: Part I

Reading Text Files

When reading characters from a file using the default character encoding, the following two procedures for setting up an InputStreamReader can be used.

Setting up an InputStreamReader which is chained to a FileInputStream (Figure 20.5(a)):

Figure 20.5 Setting Up Readers to Read Characters

Create a FileInputStream:

Click here to view code image

FileInputStream inputFile = new FileInputStream(“info.txt”);

Create an InputStreamReader which is chained to the FileInputStream:

Click here to view code image

InputStreamReader reader = new InputStreamReader(inputFile);

The InputStreamReader uses the default character encoding for reading the characters from the file.

Setting up a FileReader which is a subclass of InputStreamReader (Figure 20.5(b)):

Create a FileReader:

Click here to view code image

FileReader fileReader = new FileReader(“info.txt”);

This is equivalent to having an InputStreamReader chained to a FileInputStream for reading the characters from the file, using the default character encoding.

If a specific character encoding is desired for the reader, the first procedure can be used (Figure 20.5(a)), with the encoding being specified for the InputStreamReader:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileInputStream inputFile = new FileInputStream(“info.txt”);
InputStreamReader reader = new InputStreamReader(inputFile, utf8);

This reader will use the UTF-8 character encoding to read the characters from the file. Alternatively, we can use one of the FileReader constructors that accept a character encoding:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileReader reader = new FileReader(“info.txt”, utf8);

A BufferedReader can also be used to improve the efficiency of reading characters from the underlying stream, as explained later in this section (p. 1251).

Using Buffered Writers

A BufferedWriter can be chained to the underlying writer by using one of the following constructors:

Click here to view code image

BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

Characters, strings, and arrays of characters can be written using the methods for a Writer, but these now use buffering to provide efficient writing of characters. In addition, the BufferedWriter class provides the method newLine() for writing the platform-dependent line separator.

The following code creates a PrintWriter whose output is buffered, and the characters are written using the UTF-8 character encoding (Figure 20.6(a)):

Figure 20.6 Buffered Writers

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileOutputStream   outputFile      = new FileOutputStream(“info.txt”);
OutputStreamWriter outputStream    = new OutputStreamWriter(outputFile, utf8);
BufferedWriter     bufferedWriter1 = new BufferedWriter(outputStream);
PrintWriter        printWriter1    = new PrintWriter(bufferedWriter1, true);

The following code creates a PrintWriter whose output is buffered, and the characters are written using the default character encoding (Figure 20.6(b)):

Click here to view code image

FileWriter     fileWriter      = new FileWriter(“info.txt”);
BufferedWriter bufferedWriter2 = new BufferedWriter(fileWriter);
PrintWriter    printWriter2    = new PrintWriter(bufferedWriter2, true);

Note that in both cases, the PrintWriter is used to write the characters. The Buffered-Writer is sandwiched between the PrintWriter and the underlying OutputStreamWriter (which is the superclass of the FileWriter class).

Using Buffered Readers – Java I/O: Part I

Using Buffered Readers

A BufferedReader can be chained to the underlying reader by using one of the following constructors:

Click here to view code image

BufferedReader(Reader in)
BufferedReader(Reader in, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

In addition to the methods of the Reader class, the BufferedReader class provides the method readLine() to read a line of text from the underlying reader.

Click here to view code image

String readLine() throws IOException

The null value is returned when the end of the stream is reached. The returned string must explicitly be converted to other values.

The BufferedReader class also provides the lines() method to create a stream of text lines with a buffered reader as the data source (§16.4, p. 902).

Stream<String> lines()

Returns a finite sequential ordered Stream of element type String, where the elements are text lines read by this BufferedReader.

The following code creates a BufferedReader that can be used to read text lines from a file (Figure 20.7(b)):

Figure 20.7 Buffered Readers

Click here to view code image

// Using the UTF-8 character encoding:
Charset utf8 = Charset.forName(“UTF-8”);
FileReader     fileReader      = new FileReader(“lines.txt”, utf8);
BufferedReader bufferedReader1 = new BufferedReader(reader)
// Use the default encoding:
FileReader     fileReader      = new FileReader(“lines.txt”);
BufferedReader bufferedReader2 = new BufferedReader(fileReader);

Note that in both cases the BufferedReader object is used to read the text lines.

Java primitive values and objects cannot be read directly from their text representation in a file. Characters must be read and converted to the relevant values explicitly. If the text representation of the values is written as lines of text, each line can be read and tokenized first—that is, grouping characters into tokens that meaningfully represent a value. For example, the line “Potatoes 2.50” contains two tokens: the item token “Potatoes” and the price token “2.50”. Once a line is tokenized, the tokens can be parsed to obtain the appropriate type of value. The item token is already of type String, but the price token “2.50” needs to be parsed to a double value using the Double.parseDouble() method. A scanner provided by the java.io.Scanner class or the String.split() method called on each line can be used to tokenize the character input—both of which are beyond the scope of this book.

In contrast to Example 20.2, which demonstrated the reading and writing of binary representations of primitive data values, Example 20.4 illustrates the reading and writing of text representations of values using I/O streams that are readers and writers.

The CharEncodingDemo class in Example 20.4 writes text representations of values using the UTF-8 character encoding specified at (1). It uses a try-with-resources statement for handling the closing of I/O streams, as shown at (2) and (4). The PrintWriter is buffered (Figure 20.6(b)). Its underlying writer uses the specified encoding, as shown at (2). Values are written out with the text representation of one value on each line, as shown at (3). The example uses the same character encoding to read the text file. A BufferedReader is created (Figure 20.7(b)). Its underlying reader uses the specified encoding, as shown at (4). The text representation of the values is read as one value per line, and parsed accordingly. Each text line in the file is read by calling the readLine() method. The characters in the line are explicitly converted to an appropriate type of value, as shown at (5).

The values are printed on the standard output stream, as shown at (6). We check for the end of the stream at (7), which is signaled by the null value returned by the readLine() method of the BufferedReader class. Note the exceptions that are specified in the throws clause of the main() method.

Although buffering might seem like overkill in this simple example, for efficiency reasons, it should be considered when reading and writing characters from external storage.

It is a useful exercise to modify Example 20.4 to use the various setups for chaining streams for reading and writing characters, as outlined in this section.

Example 20.4 Demonstrating Readers and Writers, and Character Encoding

Click here to view code image

import java.io.*;
import java.nio.charset.Charset;
import java.time.LocalDate;
public class CharEncodingDemo {
  public static void main(String[] args)
         throws FileNotFoundException, IOException, NumberFormatException {
    // UTF-8 character encoding.
    Charset utf8 = Charset.forName(“UTF-8”);                           // (1)
    try(// Create a BufferedWriter that uses UTF-8 character encoding     (2)
        FileWriter writer = new FileWriter(“info.txt”, utf8);
        BufferedWriter bufferedWriter1 = new BufferedWriter(writer);
        PrintWriter printWriter = new PrintWriter(bufferedWriter1, true);) {
      System.out.println(“Writing using encoding: ” + writer.getEncoding());
      // Print some values, one on each line.                             (3)
      printWriter.println(LocalDate.now());
      printWriter.println(Integer.MAX_VALUE);
      printWriter.println(Long.MIN_VALUE);
      printWriter.println(Math.PI);
    }
    try(// Create a BufferedReader that uses UTF-8 character encoding     (4)
        FileReader reader = new FileReader(“info.txt”, utf8);
        BufferedReader bufferedReader = new BufferedReader(reader);) {
      System.out.println(“Reading using encoding: ” + reader.getEncoding());
      // Read the character input and parse accordingly.                  (5)
      LocalDate ld = LocalDate.parse(bufferedReader.readLine());
      int iMax = Integer.parseInt(bufferedReader.readLine());
      long lMin = Long.parseLong(bufferedReader.readLine());
      double pi = Double.parseDouble(bufferedReader.readLine());
      // Write the values read on the terminal                            (6)
      System.out.println(“Values read:”);
      System.out.println(ld);
      System.out.println(iMax);
      System.out.println(lMin);
      System.out.println(pi);
      // Check for end of stream:                                         (7)
      String line = bufferedReader.readLine();
      if (line != null ) {
        System.out.println(“More input: ” + line);
      } else {
        System.out.println(“End of input stream”);
      }
    }
  }
}

Output from the program:

Writing using encoding: UTF8
Reading using encoding: UTF8
Values read:
2021-06-22
2147483647
-9223372036854775808
3.141592653589793
End of input stream

Interoperability with the java.net.URI Class – Java I/O: Part II

Interoperability with the java.net.URI Class

A URI consists of a string that identifies a resource, which can be a local resource or a remote resource. Among other pertinent information, a URI specifies a scheme (e.g., file, ftp, http, and https) that indicates what protocol to use to handle the resource. The examples of URIs below show two schema: file and http. We will not elaborate on the syntax of URIs, or different schema, as it is beyond the scope of this book.

Click here to view code image

file:///a/b/c/d                      // Scheme: file, to access a local file.
http://www.whatever.com              // Scheme: http, to access a remote website.

Click here to view code image

URI(String str) throws URISyntaxException
static URI create(String str)

This constructor and this static method in the java.net.URI class create a URL object based on the specified string. The second method is preferred when it is known that the URI string is well formed.

Click here to view code image

// Create a URI object, using the URL.create(String str) static factory method.
URI uri1 = URI.create(“file:///a/b/c/d”);   // Local file.

The following methods can be used to convert a URI object to a Path object. The Paths.get(URI uri) static factory method actually invokes the Path.of(URI uri) static factory method.

Click here to view code image

// URI –> Path, using the Path.of(URI uri) static factory method.
Path uriToPath1 = Path.of(uri1);       // /a/b/c/d
// URI –> Path, using the Paths.get(URI uri) static factory method.
Path uriToPath2 = Paths.get(uri1);     // /a/b/c/d

The following method in the Path interface can be used to convert a Path object to a URI object:

Click here to view code image

// Path –> URI, using the Path.toUri() instance method.
URI pathToUri = uriToPath1.toUri();    // file:///a/b/c/d

Interoperability between a Path object and a URI object allows an application to leverage both the NIO.2 API and the network API.



The Standard Input, Output, and Error Streams – Java I/O: Part I

The Standard Input, Output, and Error Streams

The standard output stream (usually the display) is represented by the PrintStream object System.out. The standard input stream (usually the keyboard) is represented by the InputStream object System.in. In other words, it is a byte input stream. The standard error stream (also usually the display) is represented by System.err, which is another object of the PrintStream class. The PrintStream class offers print() methods that act as corresponding print() methods from the PrintWriter class. The print() methods can be used to write output to System.out and System.err. In other words, both System.out and System.err act like PrintWriter, but in addition they have write() methods for writing bytes.

The System class provides the methods setIn(InputStream), setOut(PrintStream), and setErr(PrintStream) that can be passed an I/O stream to reassign the standard streams.

In order to read characters typed by the user, the Console class is recommended (p. 1256).

Comparison of Byte Streams and Character Streams

It is instructive to see which byte streams correspond to which character streams. Table 20.7 shows the correspondence between byte and character streams. Note that not all classes have a corresponding counterpart.

Table 20.7 Correspondence between Selected Byte and Character Streams

Byte streamsCharacter streams
OutputStreamWriter
InputStreamReader
No counterpartOutputStreamWriter
No counterpartInputStreamReader
FileOutputStreamFileWriter
FileInputStreamFileReader
BufferedOutputStreamBufferedWriter
BufferedInputStreamBufferedReader
PrintStreamPrintWriter
DataOutputStreamNo counterpart
DataInputStreamNo counterpart
ObjectOutputStreamNo counterpart
ObjectInputStreamNo counterpart

20.4 The Console Class

A console is a unique character-based device associated with a JVM. Whether a JVM has a console depends on the platform, and also on the manner in which the JVM is invoked. When the JVM is started from a command line, and the standard input and output streams have not been redirected, the console will normally correspond to the keyboard and the display (Figure 20.8). In any case, the console will be represented by an instance of the class java.io.Console. This Console instance is a singleton, and can only be obtained by calling the static method console() of the System class. If there is no console associated with the JVM, the null value is returned by this method.

Figure 20.8 Keyboard and Display as Console

Click here to view code image

// Obtaining the console:
Console console = System.console();
if (console == null) {
  System.err.println(“No console available.”);
  return;
}
// Continue …

For creating dialogue for console-based applications, the Console class provides the following functionality:

  • Prompt and read a line of character-based response.

Click here to view code image

String username = console.readLine(“Enter the username (%d chars): “, 4);

The readLine() method first prints the formatted prompt on the console, and then returns the characters typed at the console when the line is terminated by the ENTER key.

  • Prompt and read passwords without echoing the characters on the console.

Click here to view code image

char[] password;
do {
    password = console.readPassword(“Enter password (min. %d chars): “, 6);
} while (password.length < 6);

The readPassword() method first prints the formatted prompt, and returns the password characters typed by the user in an array of char when the line is terminated by the ENTER key. The password characters are not echoed on the display.

Since a password is sensitive data, one recommended practice is to have it stored in memory for only as long as it is necessary and to zero-fill the char array as soon as possible in order to overwrite the password characters.

  • Print formatted values to the console.

Similar to the PrintWriter and the PrintStream classes, the Console class also provides the format() and the printf() methods for printing formatted values, but its methods do not allow a locale to be specified.

Note that the console only returns character-based input. For reading other types of values from the standard input stream, the java.util.Scanner class can be considered.

The Console class provides methods for formatted prompting and reading from the console, and obtaining the reader associated with it.

Click here to view code image

String readLine()
String readLine(String format, Object… args)

The first method reads a single line of text from the console. The second method prints a formatted prompt first, then reads a single line of text from the console. The prompt is constructed by formatting the specified args according to the specified format.

Click here to view code image

char[] readPassword()
char[] readPassword(String format, Object… args)

The first method reads a password or a password phrase from the console with echoing disabled. The second method does the same, but first prints a formatted prompt.

Reader reader()

This retrieves the unique Reader object associated with this console.

The Console class provides the following methods for writing formatted strings to the console, and obtaining the writer associated with it:

Click here to view code image

Console format(String format, Object… args)
Console printf(String format, Object… args)

Write a formatted string to this console’s output stream using the specified format string and arguments, according to the default locale. See the PrintWriter class with analogous methods (p. 1245).

PrintWriter writer()

Retrieves the unique PrintWriter object associated with this console.

void flush()

Flushes the console and forces any buffered output to be written immediately.

Example 20.5 illustrates using the Console class to change a password. The example illustrates the capability of the Console class, and in no way should be construed to provide the ultimate secure implementation to change a password.

The console is obtained at (1). The code at (2) implements the procedure for changing the password. The user is asked to submit the new password, and then asked to confirm it. Note that the password characters are not echoed. The respective char arrays returned with this input are compared for equality by the static method equals() in the java.util.Arrays class, which compares two arrays.

Example 20.5 Changing Passwords

Click here to view code image

import java.io.Console;
import java.io.IOException;
import java.util.Arrays;
/** Class to change the password of a user */
public class ChangePassword {
  public static void main (String[] args) throws IOException  {
    // Obtain the console:                                           (1)
    Console console = System.console();
    if (console == null) {
      System.err.println(“No console available.”);
      return;
    }
    // Changing the password:                                        (2)
    boolean noMatch = false;
    do {
      // Read the new password and its confirmation:
      char[] newPasswordSelected
          = console.readPassword(“Enter your new password: “);
      char[] newPasswordConfirmed
          = console.readPassword(“Confirm your new password: “);
      // Compare the supplied passwords:
      noMatch = newPasswordSelected.length == 0  ||
                newPasswordConfirmed.length == 0 ||
                !Arrays.equals(newPasswordSelected, newPasswordConfirmed);
      if (noMatch) {
        console.format(“Passwords don’t match. Please try again.%n”);
      } else {
        // Necessary code to change the password.
        console.format(“Password changed.”);
      }
    } while (noMatch);
  }
}

Running the program:

>
java ChangePassword
Enter your new password:
Confirm your new password:
Password changed.