Lesson 8: 6 May: Chapters 17, 19

Input/output through Java streams

A stream is a sequential series of bytes read from an external source or written to an external target.

The external source/target could be a file, a memory location, or a network socket.

In Java, you handle all these sources exactly the same way. This helps to make code simpler more portable.

There are two basic class hierarchies in the java.io package.

One is based on InputStream/OutputStream and reads/writes streams of bytes.

The other is based on Reader/Writer and reads/writes streams of chars.

Remember, in Java a char is a double-byte data structure.

FileInputStream

This is for reading bytes from any file. It must not be a text file, or we would be better off using a FileReader.

Note: you always need to catch IOException when using any stream class.

Book example: ReadBytes.java

import java.io.*;

public class ReadBytes {
  public static void main(String[] arguments) {
    try {
    FileInputStream file = new FileInputStream("class.dat");
    boolean eof = false;
    int count = 0;
    while (!eof) {
      int input = file.read();
      System.out.print(input + " ");
      if (input == -1)
        eof = true;
      else
        count++;
    }
   file.close();
   System.out.println("\nBytes read: " + count);
 } catch (IOException e) {
   System.out.println("Error -- " + e.toString());
 }
}
}

This is confusing: the FileInputStread.read() actually returns an int but the documentation says "Reads a byte of data from this input stream." Why is that?

Because it reads an unsigned byte value from 0 to 255, or -1 for end of file. But a Java byte type stores values from -128 to 127.

A byte is 8 bits, with 2^8 possible values == 256, which is 0 to 255 if read as unsigned.

There is a more efficient way to do the above. Instead of returning 1 byte at a time, we can use the form that fills an array of bytes.

Show doc at http://java.sun.com/j2se/1.3/docs/api/java/io/InputStream.html

In this case, it fills an array of actual byte types, which means an unsigned value. The lower numbers (0 to 127) are the same, but the higher numbers (128 to 255) are shifted to the negative range.

How confusing! I don't know why Sun decided to do it like that.

This example compares the output using both methods:

public class ReadBytesArray {
  public static void main(String[] arguments) {
    try {
    //we'll use this one to read one byte at a time
    FileInputStream file = new
    FileInputStream("testinput.txt");
    //we'll use this one to read a byte buffer
    FileInputStream file2 = new
    FileInputStream("testinput.txt");

    byte[] bb = new byte[1]; //we can make as large as we wish

    while (true) {
      //read one byte at a time: returns as an UNSIGNED byte value
    int input = file.read();
    System.out.print("A:" + input + " ");

    //read a byte array: returns the count of bytes read, puts the data in the array
    int iread = file2.read(bb);
    System.out.println("B:" + bb[0] + " ");

    if (input == -1 || iread == 0)
    break;
    }
    file.close();
    file2.close();
  } catch (IOException e) {
      System.out.println("Error -- " + e.toString());
}
}
}

FileOutputStream
Look at the book example WriteBytes.java
import java.io.*;

public class WriteBytes {
public static void main(String[] arguments) {
int[] data = { 71, 73, 70, 56, 57, 97, 15, 0, 15, 0,
128, 0, 0, 255, 255, 255, 0, 0, 0, 44, 0, 0, 0,
0, 15, 0, 15, 0, 0, 2, 33, 132, 127, 161, 200,
185, 205, 84, 128, 241, 81, 35, 175, 155, 26,
228, 254, 105, 33, 102, 121, 165, 201, 145, 169,
154, 142, 172, 116, 162, 240, 90, 197, 5, 0, 59 };
try {
FileOutputStream file = new
FileOutputStream("pic.gif");
for (int i = 0; i < data.length; i++)
file.write(data[i]);
file.close();
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
}

There is also a version write(byte[]) which, again, takes an array of signed bytes. In the above example, we use a series of unsigned byte values.

BufferedInputStream

The book uses the following metaphor: suppose instead of giving you a whole book to read, someone gave you one page at a time: you can only ask for the next page after reading the current page. What would be the effect of that?

It is inefficient because you need to wait for that next page. If instead they gave you the whole book, you would only be limited by your processing (reading) speed.

To solve this problem Java has BufferedInputStream. It reads ahead and buffers the stream data, so when you read from it, you are reading from the buffer rather than the actual stream.

To use a BufferedInputStream, you pass into the constructor another InputStream. In this way you actually create a chain of InputStreams, one feeding the other.

There is also BufferedOutputStream. The book example BufferDemo.java has both:

import java.io.*;

public class BufferDemo {

public static void main(String[] arguments) {

int start = 0;

int finish = 255;

if (arguments.length > 1) {

start = Integer.parseInt(arguments[0]);

finish = Integer.parseInt(arguments[1]);

} else if (arguments.length > 0)

start = Integer.parseInt(arguments[0]);

ArgStream as = new ArgStream(start, finish);

System.out.println("\nWriting: ");

boolean success = as.writeStream();

System.out.println("\nReading: ");

boolean readSuccess = as.readStream();

}

}

class ArgStream {

int start = 0;

int finish = 255;

ArgStream(int st, int fin) {

start = st;

finish = fin;

}

boolean writeStream() {

try {

FileOutputStream file = new

FileOutputStream("numbers.dat");

BufferedOutputStream buff = new

BufferedOutputStream(file);

for (int out = start; out <= finish; out++) {

buff.write(out);

System.out.print(" " + out);

}

buff.close();

return true;

} catch (IOException e) {

System.out.println("Exception: " + e.getMessage());

return false;

}

}

boolean readStream() {

try {

FileInputStream file = new

FileInputStream("numbers.dat");

BufferedInputStream buff = new

BufferedInputStream(file);

int in = 0;

do {

in = buff.read();

if (in != -1)

System.out.print(" " + in);

} while (in != -1);

buff.close();

return true;

} catch (IOException e) {

System.out.println("Exception: " + e.getMessage());

return false;

}

}

}

Data Streams

Sometimes you will want to read and write various data types other than raw bytes or characters:

primitive types such as booleans, double, float, int, long, etc.

The DataOutputStream automatically converts each of these into byte values which can be written to an OutputStream.

If you write to a file, it will not have a standard file format. It will be pretty much a raw sequence of byte values which another program has to know how to read.

Book example: WritePrimes.java

In this example, it chains 3 OutputStreams together: DataOutputStream writes to a BufferedOutputStream writes to a FileOutputStream.

import java.io.*;

class WritePrimes {

public static void main(String[] arguments) {

int[] primes = new int[400];

int numPrimes = 0;

// candidate: the number that might be prime

int candidate = 2;

while (numPrimes < 400) {

if (isPrime(candidate)) {

primes[numPrimes] = candidate;

numPrimes++;

}

candidate++;

}

try {

// Write output to disk

FileOutputStream file = new

FileOutputStream("400primes.dat");

BufferedOutputStream buff = new

BufferedOutputStream(file);

DataOutputStream data = new

DataOutputStream(buff);

for (int i = 0; i < 400; i++)

data.writeInt(primes[i]);

data.close();

} catch (IOException e) {

System.out.println("Error -- " + e.toString());

}

}

public static boolean isPrime(int checkNumber) {

double root = Math.sqrt(checkNumber);

for (int i = 2; i <= root; i++) {

if (checkNumber % i == 0)

return false;

}

return true;

}

}

The counterpart is ReadPrimes.java

import java.io.*;

class ReadPrimes {

public static void main(String[] arguments) {

try {

FileInputStream file = new

FileInputStream("400primes.dat");

BufferedInputStream buff = new

BufferedInputStream(file);

DataInputStream data = new

DataInputStream(buff);

try {

while (true) {

int in = data.readInt();

System.out.print(in + " ");

}

} catch (EOFException eof) {

buff.close();

}

} catch (IOException e) {

System.out.println("Error -- " + e.toString());

}

}

}

Reading/Writing Character Streams (text files)

Instead of using the InputStream/OutputStream hiearchy, use the Reader/Writer hierarchy.

Reader and Writer work with char types, which as you know are 2-byte unicode characters. So there is no special trickery needed to deal with non-Roman characters (such as Kanji, Korean, Syrillic, Greek, etc)

Book example showing a BufferedReader: ReadSource.java

Features the useful readLine() method which returns a whole String

import java.io.*;

public class ReadSource {

public static void main(String[] arguments) {

try {

FileReader file = new

FileReader("ReadSource.java");

BufferedReader buff = new

BufferedReader(file);

boolean eof = false;

while (!eof) {

String line = buff.readLine();

if (line == null)

eof = true;

else

System.out.println(line);

}

buff.close();

} catch (IOException e) {

System.out.println("Error -- " + e.toString());

}

}

}

There is also a BufferedWriter that works much the same way.

Any time you use a Reader or Writer, there is a character encoding involved: that's how double-byte characters are translated into single bytes for writing to files or other destinations.

You can find out the default character encoding from System.getProperty("file.encoding") (demo)

On my system it's something called Cp1252

To guarantee full Unicode compatibility, you should use an encoding called UTF8. Here's an example from the tutorial of writing a UTF8 file (UTF8Example.java):

static void writeOutput(String str) {

try {

FileOutputStream fos = new FileOutputStream("test.txt");

Writer out = new OutputStreamWriter(fos, "UTF8");

out.write(str);

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

Networking in Java

URLs

The first thing you need to know about is a URL which stands for Uniform Resource Locator.

Example: http://java.sun.com/index.html - the String form of a URL. This consists of a protocol (http), hostname (java.sun.com) and pathname (/index.html).

In Java, you can use the String form of URL, but it is more convenient to work with the java.net.URL class.

applet context

The book has the following example of an applet that makes the containing environment (the browser) open new URLs.

It uses getAppletContext().showDocument()

WebMenu.java:

import java.net.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class WebMenu extends JApplet implements ActionListener {

WebButton[] choices = new WebButton[3];

public void init() {

choices[0] = new WebButton("Obscure Store",

"http://www.obscurestore.com/");

choices[1] = new WebButton("Need to Know",

"http://www.ntk.net/");

choices[2] = new WebButton("Bleat",

"http://www.lileks.com/bleats");

FlowLayout flo = new FlowLayout();

getContentPane().setLayout(flo);

for (int i = 0; i < choices.length; i++) {

choices[i].addActionListener(this);

getContentPane().add(choices[i]);

}

}

public void actionPerformed(ActionEvent evt) {

WebButton clicked = (WebButton)evt.getSource();

try {

URL load = new URL(clicked.address);

getAppletContext().showDocument(load);

} catch (MalformedURLException e) {

showStatus("Bad URL:" + clicked.address);

}

}

}

class WebButton extends JButton {

String address;

WebButton(String iLabel, String iAddress) {

super(iLabel);

address = iAddress;

}

}

 

URL Connections

What if your applet itself needs to open a stream from a web server? For example, read an XML configuration file from the server at init time.

The simplest way is to use a class called java.net.URLConnection.

But a normal (unsigned) applet can only talk to one host: the host from which the class file was loaded. This is a security restriction.

You can get around this restriction (and others) by signing your applet (that's complicated, we'll talk about it later).

Book example: an application that opens any web page and prints out the raw HTML (doesn't try to render it). GetFile.java

import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

import java.net.*;

import java.io.*;

public class GetFile {

public static void main(String[] arguments) {

if (arguments.length == 1) {

PageFrame page = new PageFrame(arguments[0]);

page.show();

} else

System.out.println("Usage: java GetFile url");

}

}

class PageFrame extends JFrame {

JTextArea box = new JTextArea("Getting data ...");

URL page;

public PageFrame(String address) {

super(address);

setSize(600, 300);

JScrollPane pane = new JScrollPane(box);

getContentPane().add(pane);

WindowListener l = new WindowAdapter() {

public void windowClosing(WindowEvent evt) {

System.exit(0);

}

};

addWindowListener(l);

try {

page = new URL(address);

getData(page);

} catch (MalformedURLException e) {

System.out.println("Bad URL: " + address);

}

}

void getData(URL url) {

URLConnection conn = null;

InputStreamReader in;

BufferedReader data;

String line;

StringBuffer buf = new StringBuffer();

try {

conn = this.page.openConnection();

conn.connect();

box.setText("Connection opened ...");

in = new InputStreamReader(conn.getInputStream());

data = new BufferedReader(in);

box.setText("Reading data ...");

while ((line = data.readLine()) != null)

buf.append(line + "\n");

box.setText(buf.toString());

} catch (IOException e) {

System.out.println("IO Error:" + e.getMessage());

}

}

}

You can see that URLConnection has a pretty simple usage. It just returns you the text of the document. You don't need to negotiate the HTTP protocol: it does that for you.

You can also get HTTP header information from the URLConnection (available in netscape from view|page info).

sockets

If you can't do it through a URLConnection, you can probably use the lower-level interface known as sockets.

A socket is a point-to-point connection across a network involving a port number.

The URLConnection actually goes through a socket interface, but in that case the socket level is hidden from you the programmer.

When you communicate using sockets, you are not restricted to the protocols specified in a URL-- you can even make up your own protocol if you need to.

Book example: Finger.java

import java.io.*;

import java.net.*;

import java.util.*;

public class Finger {

public static void main(String[] arguments) {

String user;

String host;

if ((arguments.length == 1) && (arguments[0].indexOf("@") > -1)) {

StringTokenizer split = new StringTokenizer(arguments[0],

"@");

user = split.nextToken();

host = split.nextToken();

} else {

System.out.println("Usage: java Finger user@host");

return;

}

try {

Socket digit = new Socket(host, 79);

digit.setSoTimeout(20000);

PrintStream out = new PrintStream(digit.getOutputStream());

out.print(user + "\015\012");

BufferedReader in = new BufferedReader(

new InputStreamReader(digit.getInputStream()));

boolean eof = false;

while (!eof) {

String line = in.readLine();

if (line != null)

System.out.println(line);

else

eof = true;

}

digit.close();

} catch (IOException e) {

System.out.println("IO Error:" + e.getMessage());

}

}

}

This is a very simple example because the finger server simply returns a text string. There is no complex protocol to deal with.

Note the new Socket(host, 79). That "79" is the agreed-upon port number for finger servers.

try with khoekstra@ravensoft.com

Real world example: auto-updater

Show sourcecode from aerosmith project

Explain about the signed applet

Server Sockets

Book has example of using a ServerSocket class and accept() method to get connections from a client.

accept() blocks until a connection is received.

In file TriviaServer.java