Title: Character Stream Classes
Course: CPS 261: Advanced Java Concepts ~ DL & MM MASTER

Character Stream Classes

So far we have worked with the so-called Byte Stream classes. Note that all of our classes that end with "Stream" are in the byte world. If our data is binary (i.e. objects, ints, images, etc.), we for sure want to work with the Byte stream world.

However, there is another world of the human readable character characters. When Java first came out, Java only had byte, and ASCII character concepts. Consequently, you will note that the following happen to be in the Byte world (but probably shouldn't be):

System.in - is an InputStream

System.out - is a PrintStream

Internationalization -

When it was clear that Java needed to work in countries that use something quite different from ASCII (i.e. Chinese), Java had to come up with an approach to deal with this. So here are the issues:

- char is 2 bytes - For any given country, it turns out that 2 bytes is large enough to store the characters necessary. Even though in Western countries a 1 byte ASCII character will work.
- 4 bytes are needed to store all of the characters necessary for all languages supported by Java.
- UTF-8 is the dominate encoding/decoding approach for dealing with characters. UTF-8 is backwards compatible with simple ASCII. In otherwords, if you get a UTF-8 byte which is 0 - 127, then it exactly represents an ASCII character. If you get a byte which is >= 128 then we are probably representing a character that requires 2, 3, or 4 bytes. In this case we could be representing a Chinese character.
- Files containing characters should be encoded into something like UTF-8. However, when these files are read in, we need to store these characters into 2 byte char primitives. The value stored will depend on the "locale" or country where the program is running.
- Since I have mostly lived in an ASCII world, I really haven't had a need to deal with this issue too much. However, the good news is that Java provides a parallel set of classes that you can use to deal with this ugliness.

Reader/Writer world.

To come away with a solution that works with human characters and should work in any country,you will use Reader/Writer classes instead of InputStream/OutputStream classes. The Reader/Writer classes are supposed to take care of the dirty internationalization business. I have provided the following table to show the basic idea.

InputStream/Reader World (Usually just replace InputStream with Reader works)

Byte World | Char World | Description
InputStream | Reader | The low level unbuffered abstract class. Not directly constructable.
FileInputStream | FileReader | These 2 classes create InputStream/Reader classes connected to a disk file.
BufferedInputStream | BufferedReader | These 2 classes are used to buffer InputStream/Reader classes

OutputStream/Writer World (Usually just replace OutputStream with Writer works)

Byte World | Char World | Description
OutputStream | Writer | The low level unbuffered abstract class. Not directly constructable.
FileOutputStream | FileWriter | These 2 classes create OutputStream/Writer classes connected to a disk file.
BufferedOutputStream | BufferedWriter | These 2 classes are used to buffer OutputStream/Writer classes
PrintStream | PrintWriter | These 2 classes are used to create classes that support print, println methods.

Note that the Scanner class seems to be tied to an InputStream and I don't know of a way to tie it to a Reader class.

To show the symmetry between the Byte world and the Char world, I have repeated some of our previous examples:

Low Level Copy Routine

import java.io.*;

public class Copy {
    static void byteCopy(String from, String to) throws IOException
    {
    // Using Non-buffered Low Level I/O byte stream operations

        int b;
        // Using byte stream  (8 bit)
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to);

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

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

    static void charCopy(String from, String to) throws IOException
    {
    // Using Non-buffered Low Level I/O char stream operations

        int c;
        // Using Unicode Approach char stream  (16 bit)
        FileReader in = new FileReader(from);
        FileWriter out = new FileWriter(to);

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

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

    public static void main(String[] args) throws IOException {
        charCopy("Copy.java", "junk1.txt");
        byteCopy("junk1.txt", "junk2.txt");
    }
}




Adding buffering to your application - To add buffering to your application, you wrap your stream or your Reader class with a buffering class. As the following example shows, the code can look mostly the same, we are just adding buffering support. The buffering constructors take as a parameter an instance of the low level input source. For example:

import java.io.*;

public class CopyBuffered {
    static void byteCopy(String from, String to) throws IOException
    {
       int b;
    // Using Buffered I/O byte(8 bit) stream operations

        FileInputStream insrc = new FileInputStream(from);
        FileOutputStream outsrc = new FileOutputStream(to);
        BufferedInputStream in = new BufferedInputStream(insrc);
        BufferedOutputStream out = new BufferedOutputStream(outsrc);


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

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

    static void charCopy(String from, String to) throws IOException
    {
     int c;

    // Using Buffered I/O char(16 bit Unicode) stream operations

        FileReader insrc = new FileReader(from);
        FileWriter outsrc = new FileWriter(to);
        BufferedReader in = new BufferedReader(insrc);
        BufferedWriter out= new BufferedWriter(outsrc);


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

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

    public static void main(String[] args) throws IOException {
        charCopy("CopyBuffered.java", "junk1.txt");
        byteCopy("junk1.txt", "junk2.txt");
    }
}




Conversions from the Byte world to the Char world.

If you can't use the Scanner class then you will probably run into the problem of converting from the Byte world to the Character world. The earlier versions of Java (before 1.4?) didn't have the Scanner class so we had to deal with this issue because System.in is an InputStream.

InputStream --> Reader


import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;


public class Convert {

    public static void main(String[] args) throws IOException
    {
        InputStream is = System.in;
        Reader reader = new InputStreamReader(is);

    }

}


There are times when you may have an output source from the byte world and you want to convert it to something in the Character world. I personally haven't run into this issue in my own work. However, the following shows how this could be done:

OutputStream ---> Writer


import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;


public class Convert {

    public static void main(String[] args) throws IOException
    {
        FileOutputStream fos = new FileOutputStream("something.txt");
        Writer myWriter = new OutputStreamWriter(fos);

        // The above should be equivalent to:
        Writer myWriter2 = new FileWriter("something.txt");

    }

}


Reading from the Keyboard without the Scanner class


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class MyInput {

    BufferedReader in = null;

    // read from keyboard
    public MyInput()  {
        InputStreamReader reader = new InputStreamReader(System.in);
        in = new BufferedReader(reader);
    }

    //read from file f
    public MyInput(String filename)
    {
        try {
            FileReader fr = new FileReader(filename);
            in =    new BufferedReader(fr);
        }
        catch (FileNotFoundException e) {
            System.out.println("file: "+filename +" not found ... defaulting to the keyboard");
        }
    }

    public void close()
    {
        try {
            if (in != null)
                in.close();
        }
        catch (IOException e){
            System.out.println("IO Exception on close call");
        }
    }

    protected void finalize()
    {
        close();
    }

    public String get()
    {
        try {
            return in.readLine();
        }
        catch (FileNotFoundException e) {
            System.out.println("File Not Found Exception in get");
            return null;
        }
        catch(IOException e) {
            System.out.println("IO Exception in get");
            return null;
        }
    }

    public int getInt()
    {
        String str = get();
        if (str != null)
        {
            try {
                return Integer.parseInt(str);
            }
            catch (NumberFormatException e) { /* Do Nothing */}
        }
        System.out.println("Bad int input");
        return -1;
    }

    public static void main(String args[])  {
        MyInput input;
        String str;

        if (args.length > 0)
            input = new MyInput(args[0]);
        else
            input = new MyInput();

        do {
            str = input.get();
            if (str != null)
            {
                System.out.println(str);
                if (str.equals("####"))
                    break;
            }
        } while (str != null);

        input.close();
        System.out.println("Bye");

    }
}


Last Updated: August 1, 2015 0:19 AM