I/O

In this part we will learn about input/output (I/O) in Java, focusing specifically on file I/O. We will also learn how to read and write from the console.

Java IO vs NIO

Java provides two major frameworks in packages java.io and java.nio. The main differences between these frameworks are:

IO NIO
Stream-oriented Buffer-oriented
Blocking Non-blocking

Here we will focus mainly on the IO framework.

Java IO

java.io provides several types of abstractions for

Input and Output Streams

A basic mechnism for I/O in the java.io framework are the InputStream https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html and OutputStream abstract classes https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/OutputStream.html.

These allow reading (InputStream) and writing (OutputStream) binary data in a blocking fashion. That is once you call a method to read or write from a stream, the execution of your program is paused while waiting for a result (unless an Exception is thrown to be thrown). We already discussed some I/O related concepts when talking about exception handling. Let us discuss some methods of InputStream and OutputStream:

  • InputStream
    • close() - closes the stream and releases all system resources associated with it. Always close streams when you are done with them!
    • int available() - returns an estimate of the number of bytes that are available for reading
    • int read() - reads a single byte from the stream. This method blocks until a byte is available.
    • int read (byte[] b) - reads a number of bytes into buffer b and returns the number of bytes that have been read
  • OutputStream
    • close() - closes the stream and releases all system resources associated with it. Always close streams when you are done with them!
    • write (int b) - write a single byte to the output stream
    • write(byte[] b) - write the content of byte array b to the output stream
    • flush() - flushes this output stream which causes any buffered content (if the outputstream does buffering) to be written out

Some noteworthy subclasses are:

  • FileInputStream - which reads data from a file
  • FileOutputStream - writes data to a file
  • BufferedInputStream - wraps another input stream and adds buffers reads
  • BufferedOutputStream - wraps an output stream and adds buffering of writes
  • ObjectInputStream - read primitive java values and objects from an input stream in a machine-architecture independent way (data is expected to have been written by ObjectOutputStream. Only classes that implement the interface java.io.Serializable or java.io.Externalizable can be read or written using this meachnism.
  • ObjectOutputStream - write primitive java values and objects as explained above for ObjectInputStream

Reader and Writer

The abstract Reader and Writer classes are superclasses for all classes that read or write character streams. The main difference input and output streams is that data is interpreted as characters instead of raw bytes. Relevant methods are:

  • Reader
    • close() - close the stream and release resources
    • boolean ready() - is the stream ready for reading?
    • int read() - reads a single character
    • int read(char[] cbuf) - reads characters into buffer cbuf, returns the number of characters that were read
    • skip(long n) - skips n characters
  • Writer
    • close() and flush() behave like for streams
    • write(String str) - write the content of string to the writer

Some subclasses of interest:

  • StringReader - reads from a String.
  • StringWriter - writes to a StringBuffer
  • BufferedReader/Writer - buffers a reader or writer
  • InputStreamReader - wraps an input stream to read characters from it
  • FileReader/Writer - reads/writes from/to a file
In [1]:
import java.io.StringReader;

// Reading from String
StringReader r = new StringReader("aabbaa");
int read;

while((read = r.read()) != -1)
    System.out.println((char) read);
a
a
b
b
a
a
Out[1]:
null
In [2]:
import java.io.FileInputStream;
import java.io.File;
import java.io.IOException;

FileInputStream in = null;
File f;
byte[] b = new byte[256]; // 256 byte buffer
        
System.out.println("try to open test.txt");
f = new File("test.txt");
try {
    if (f.exists()) {
        System.out.println("File exists, try to read");
        in = new FileInputStream(f);
        for(int available = in.available(); available > 0; available = in.available()) { // read data using buffer b
            int numread = in.read(b);
            System.out.printf("\nread %d bytes: ", numread);
            for(int i = 0; i < numread; i++) // print all bytes read and the a newline
                System.out.print((char) b[i]);
            System.out.println();
        }
    }
    else {
        System.out.println("File does not exist!");
    }
}
catch (IOException e) {
    System.out.println("error reading text.txt: " + e.toString());
}
finally {
    if (in != null) {
        try { 
            in.close();
            System.out.println("closed file!"); // look out for this in the output. This statement is excuted even though no exception has been thrown.
        }
        catch (IOException e) {
            System.out.println("failed closing file input stream:\n" + e.toString());
        }
    }
}
try to open test.txt
File exists, try to read

read 256 bytes: This is a file with a long text the is split over multiple lines. We want to
show how our buffer is exausted and how we read this long text 256 bytes at a
time. That is why I have to ramble on and on and on and on and on and on and on
and on and on and on 

read 256 bytes: and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and 

read 256 bytes: on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on a

read 256 bytes: nd on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and o

read 256 bytes: n and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on an

read 167 bytes: d
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on.

closed file!
Out[2]:
null

Serializable Interface

Java allows objects to be written and read from streams using ObjectOuputStream and ObjectInputStream which wrap an OutputStream and InputStream, e.g., a FileInputStream to read from a file. This interface does not define any methods. Per default, all non-static fields are serialized and deserialized. A class implementing Serializable should be define a default constructor (no arguments). More details of serialization and deserialization in Java can be found in the documentation.

In [1]:
package lecture;

import java.io.Serializable;

public class SerPerson implements Serializable {
    private String name;
    
    public SerPerson() {
        this.name = null;
    }
    
    public SerPerson(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
}
Out[1]:
lecture.SerPerson
In [2]:
import lecture.SerPerson;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

File f = new File("person_test.txt");
SerPerson p = new SerPerson("Peter");

// write p to a file
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(p);
out.close();

// read p from the file
ObjectInputStream in = new ObjectInputStream(new FileInputStream(f));
p = (SerPerson) in.readObject();
in.close();

if(f.exists())
    f.delete();

return p.getName();
Out[2]:
Peter