Java 2 API: java.io

InputStream/OutputStream vs. Reader/Writer

The streams came first, and were byte-oriented classes. In JDK 1.1, readers/writers were introduced to handle 16 bit unicode character input. Readers/writers are locale-dependent. There are bridge classes which allow you to switch between streams and readers/writers.

Basic I/O

Java's io package was designed in a very "object-oriented" way. The notion was that the actual implementation did not matter. So in Java, writing to a network socket looks the same as writing to a file, and writing arabic text looks the same as writing english, at least as far as the interface is concerned. This is a good thing. Unfortunately, the Java designers chose not to provide convenience classes to make some common kinds of i/o easier. Therefore, even simple i/o in Java can be a bit confusing at first.

To interact with the user at the command line, Java provides the streams System.in, System.out, and System.err These streams are pointed to the console by default, but you can redirect them if you like. However, in advanced applications, logging is not done using System.out and System.err. Instead there are logging frameworks for this purpose, e.g. log4j.

Console I/O Example

package ca.ucalgary.conted.introjava.console;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;

public class ConsoleIO {
    public static void main(String[] args) throws Exception {

        BufferedReader reader = getConsoleReader();
        System.out.print("enter number: ");
        int number = Integer.parseInt(reader.readLine());

        for (int i=0; i<20; i++)
            System.out.println();

        boolean ok = true;
        int guess = 0;
        while (ok) {
            System.out.print("guess number: ");
            guess = Integer.parseInt(reader.readLine());
            if (guess == number) {
                System.out.println("you guessed it!");
                break;
            }

            System.out.print("wrong guess! continue (y/n)?");
            String continuePlaying = reader.readLine();
            if (continuePlaying.toUpperCase().equals("N")) {
                System.out.println("better luck next time!");
                break;
            }
        }

        System.out.println("game over!");
        System.exit(0);
    }

    public static BufferedReader getConsoleReader() {
        InputStream is = null;
        InputStreamReader isReader = null;
        BufferedReader bufferedReader = null;

        try {
            is = System.in;
            isReader = new InputStreamReader(is);
            bufferedReader = new BufferedReader(isReader);
        } catch (Exception e) {
        }

        return bufferedReader;
    }
}

File I/O

File I/O uses the same Stream/Reader/Writer concepts. Again, there is a FileInputStream/FileOutputStream combination based no file that contain characters as bytes (ASCII encoding), and there is a UNICODE based FileReader/FileWriter combination, which supports more advanced kinds of text. The FileReader/FileWriter encodes the text based on your Locale. We may discuss this issue which is called Internationalization in a later part of  the course if time permits. It is important to note that in Java a File object is not a link to a file on the actual disk. It is just an object that stores information about a file. You can ask about the file size, wether such a file does exist on the disk, and you can ask it to be deleted or created on the disk, but you can't write to a file directly using just a File object as you can in other programming languages.

import java.io.*;

public class Copy {
public static void main(String[] args) throws IOException {
File inputFile = new File("farrago.txt");
File outputFile = new File("outagain.txt");

FileReader in = new FileReader(inputFile);
FileWriter out = new FileWriter(outputFile);
int c;

while ((c = in.read()) != -1)
out.write(c);

in.close();
out.close();
}
}

Serializing Objects

You can use ObjectInputStream and ObjectOutputStream to serialize objects. In order for Java to be willing to serialize an object, it has to implement a special interface called Serializable. The following example uses the Card and WarCardComparator classes listed in the notes on Collections. In order to make it work however, you'll need to modify the Card class so that it implements java.io.Serializable. In other words, change

public class Card {

to

public class Card implements java.io.Serializable

The code below demonstrates how to implement a trivial object database in Java. The reason it is trivial is that it serializes an entire collection of objects to a file, and the only way to retrieve the collection is all at once into memory. Real databases allow you to retrieve subsets of the total data into memory. Say you've got a database with a table that contains 30 million records. It is probably not a good idea to load all 30 million records into RAM when all you want to do is view a single record (note: even if each record is only 10 bytes in size, 30 million  records add up to 300 MB!; not only would doing this tax the server, the performance would be awful). Serialization isn't used primarily for object storage in terms of a database as in this example. In real life serialization is used in RMI and EJB to transfer objects across networks (distributed computing), and for activation and passivation of Enterprise JavaBeans, for example as part of load balancing or resource management.

package ca.ucalgary.conted.introjava.cardgames.gameofwar

import java.util.Stack;
import java.io.*;

public class SerializeCollection {
    public static void main(String[] args) throws Exception {
        Card fiveHearts = new Card(Card.HEARTS, Card.FIVE, Card.FACE_UP);
        Card queenDiamonds = new Card(Card.DIAMONDS, Card.QUEEN, Card.FACE_UP);
        Card eightSpades = new Card(Card.SPADES, Card.EIGHT, Card.FACE_UP);
        Card tenClubs = new Card(Card.CLUBS, Card.TEN, Card.FACE_UP);

        Stack deck = new Stack();
        deck.push(fiveHearts);
        deck.push(queenDiamonds);
        deck.push(eightSpades);
        deck.push(tenClubs);

        System.out.print("Original deck: ");
        System.out.println(deck);

        File deckDB = new File("deck.db");

        FileOutputStream fos = new FileOutputStream(deckDB);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(deck);
        oos.close();

        FileInputStream fis = new FileInputStream(deckDB);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Stack deck2 = (Stack) ois.readObject();
        ois.close();

        System.out.print("Deck read from database file: ");
        System.out.println(deck2);
    }
}

Socket I/O

Below is a very trivial socket I/O example demonstrating a simple EchoServer and an EchoClient program that uses it. As you can see, once you master the basics of I/O in Java, it is not very hard to apply this knowledge to many different areas.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {
    public static void main(String[] args) throws Exception {

        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(4444);
        } catch (IOException e) {
            System.err.println("Could not listen on port: 4444.");
            System.exit(1);
        }

        Socket clientSocket = null;
        try {
            System.out.println("server: starting server...");
            clientSocket = serverSocket.accept();
            System.out.println("server: established connection with client...");
        } catch (IOException e) {
            System.err.println("Accept failed.");
            System.exit(1);
        }

        System.out.println("server: getting input and output streams");

        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        System.out.println("server: got input");

        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
        System.out.println("server: got output");

        String inputLine, outputLine;

        inputLine = in.readLine();
        System.out.println("server: got command: " + inputLine);
        outputLine = processInput(inputLine);
        out.println(outputLine);
        out.flush();

        out.close();
        in.close();
        clientSocket.close();
        serverSocket.close();
    }

    private static String processInput(String inputLine) {
        return "ack: " + inputLine;
    }
}
 
//------------------------------------------------------

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class EchoClient {
    public static void main(String[] args) {
        EchoClient client = new EchoClient();
        client.sendData();
    }

    public void sendData() {
        String command = "hello there!";

        try {
            Socket connection = new Socket("localhost", 4444);
            PrintWriter out = new PrintWriter(connection.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

            out.write(command.toString() + "\n");
            out.flush();
            System.out.println("client: sent command " + command.toString());
            String response = in.readLine();
            System.out.println("client: got response = " + response);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}