Output

At last we're at the point where we'll talk about one of the major confusions in Java I/O, use of char (double byte) versus single byte I/O.

Readers versus OutputStreams

Originally, Java only had stream classes, and the streams only operated on bytes of data. However, characters in Java are two bytes wide, and byte-oriented I/O did not properly cope with internationalization.

So a wider type of stream was introduced in JDK 1.1 specifically for character-based I/O. Reader classes are able to get Unicode character input two bytes at a time. Writer classes are able to do Unicode character output two bytes at a time, as shown in Figure 17-2. Input and output streams operate on data one byte at a time.

Figure 17-2. Your program outputs data into a stream or writer

image

Java I/O is overly complicated to the newcomer because it relies on layering or wrapping classes. You need two or three classes to do anything. You determine exactly what classes to use by reviewing three attributes of your output:

  • Whether you need 2-byte characters

  • What you will be writing to (file, socket etc.)

  • What format you want to write (binary or printable)

Here are the steps for choosing the classes in detail:

  1. Decide if your output includes any characters. If it does, do you want to output 16-bit characters or 8-bit characters?

    If you are going to print a report in the USA or Europe, you'll probably use and choose 8-bit characters (apart from anything else, your printer probably can't print arbitrary 16-bit characters). If you have a database that uses Unicode, choose 16-bit. You want to make a choice that doesn't lose data.

    • 8-bit characters use an OutputStream class. Choose this by default.

    • 16-bit characters use a Writer class. Choose this if you want to write characters that have values outside the 8-bit byte ISO 8859-1 character set, such as Chinese or Hindi characters.

  2. Decide where you are going to write the data to.

    Java programs access external data by instantiating a stream on the data. Most physical destinations (a place to where an output stream can flow) have a class dedicated to writing output there. There is only a limited number of destinations for data, shown in Table 17-4.

    Table 17-4. Output destination and data width, determine the bottom layer class

    image

    You write to files, pipes, and in-memory data by instantiating a class that knows how to write to that destination. Sockets and URLs are treated differently because they can be read as well as written. Instead of instantiating a class, you call the getOutputStream() method on a socket or URLConnection object. Writing to a URL takes a little bit of URL-specific setup (you have to open a connection, create a connection object, configure the server to allow writing, etc.), but the other four destinations are very easy to use. Most of the time, you'll be writing to a file.

    Let's call the class that you choose in this step the bottom-layer class.

  3. Decide what kind of output you will do, binary or printable.

    You can output binary values and 8-bit characters, of which the ASCII characters are just a subset. The full 8-bit character set is known as ISO 8859-1 Latin-1, but few have heard of that name and everyone has heard of ASCII, hence the title of Table 17-5. Both ASCII and 8859-1 are listed in Appendix B so you can review them.

    Table 17-5. Differences among binary, ASCII, and String output determine the top layer class

    Type

    Format

    Hexadecimal value

    Class to use

    Binary

    4-byte binary integer

    0000715B

    java.io.DataOutputStream

    ASCII bytes

    Successive bytes

    32 39 30 31 39

    java.io.PrintOutputStream

    String

    Successive double-byte characters

    0032 0039 0030 0031 0039

    java.io.PrintWriter

    As you probably know, there are different bit representations for same value held as different types. Take the number 29,012 as an example. It can be represented in a computer in (at least) three ways, as shown in Table 17-5.

    These three different representations correspond to the three classes in java.io for writing output: DataOutputStream, PrintOutputStream, and PrintWriter. There's a fourth option too, namely to write out an entire object with all its fields. This is known as serializing an object, and we will look at it in a subsequent chapter.

    Notice that it's not enough to say “I want to output the number 29012”. You have to decide if you want to write it in binary or in a printable form. If a printable form, you will use a PrintWriter or PrintOutputStream to match your decision on 16-bit or 8-bit characters in step 1.

    Let's call the class that you choose in this step the top-layer class.

Practical examples of output

Now that we know how to choose which classes to layer for output, let's look at three practical examples.

Example 1: You have an array of 1000 int values, and you want to put them in a file as binary data.

This output doesn't include any characters, so we know we're dealing with 8-bit output streams, not writers. The destination is a file, so consulting Table 17-4 the bottom-layer class will be java.io.FileOutputStream. Unless there is a special reason to make the output printable, we will save the data as binary ints. Table 17-5 tells us that the top-layer class is therefore java.io.DataOutputStream. When we wrap, the code will look like:

FileOutputStream fos = new FileOutputStream("ints.dat");
DataOutputStream dos = new DataOutputStream( fos );

This is almost always written in shortened form:

DataOutputStream dos = new DataOutputStream(
                           new FileOutputStream("ints.dat") );

We can now use methods of DataOutputStream to write data. These methods are listed in the next section, java.io.DataOutputStream for binary output.

Example 2: You want to write out the company book of spare parts, which is held in a Map of Integer, String pairs representing part number and part description. All the part descriptions can be accurately represented in ASCII characters.

The output does include characters, and the specification makes clear that 8-bit characters are adequate. Again the destination is a file, and Table 17-4 shows that the bottom layer class will be java.io.FileOutputStream. Since this is a phonebook, we surely want the output numbers to be printable ASCII bytes. Table 17-5 shows that the right top layer class is java.io.PrintStream. The code will be:

PrintStream pos = new PrintStream(
                       new FileOutputStream("phonebk.txt") );

We will then use methods of PrintStream to write the ints and Strings.

Example 3: You have an array of all the names of employees in the Bangalore office. The names are in Hindi and other character sets. You want to write the names into a pipe to some other thread.

The specification makes clear that 16-bit characters are needed, combined with output to a pipe. Matching this in Table 17-4 shows that the bottom layer class will be java.io.PipedWriter. Table 17-5 shows that we will use java.io.PrintWriter as our top layer class. The code is:

PipedWriter piw = new PipedWriter();
PrintWriter pw = new PrintWriter( piw );
// we'll also need this to read from the other end of the pipe
PipedReader pir = new PipedReader(piw);

We will then use methods of PrintWriter to write the Strings.

Now that we have identified the three top-layer classes, the next three sections show the methods of the classes.

java.io.DataOutputStream for binary output

Here are the signatures of methods in the DataOutputStream class.

public class java.io.DataOutputStream
                  extends java.io.FilterOutputStream
                    implements java.io.DataOutput {
  // constructor
    public DataOutputStream(java.io.OutputStream);
    public final void writeBoolean(boolean) throws java.io.IOException;
    public final void writeByte(int) throws java.io.IOException;
    public final void writeShort(int) throws java.io.IOException;
    public final void writeChar(int) throws java.io.IOException;
    public final void writeInt(int) throws java.io.IOException;
    public final void writeLong(long) throws java.io.IOException;
    public final void writeFloat(float) throws java.io.IOException;
    public final void writeDouble(double) throws java.io.IOException;
    public final void writeBytes(java.lang.String) throws java.io.IOException;
    public final void writeChars(java.lang.String) throws java.io.IOException;
    public final void writeUTF(java.lang.String) throws java.io.IOException;

    public void flush() throws java.io.IOException;
    public synchronized void write(int) throws java.io.IOException;
    public synchronized void write(byte[], int, int) throws java.io.IOException;
    public final int size();  // returns number-of-bytes written so far
}

Using DataOutputStream we can write the code to complete example 1, and output the int variables to a file. The whole thing will look like this:

           // output binary data to a File
DataOutputStream dos = new DataOutputStream(
                              new FileOutputStream("ints.dat") );
// put next line in a for loop:
    dos.writeInt( myArray[i] );
// when done writing, close the file:
    dos.close();

Whenever you do I/O, you usually need to wrap the statements in a handler for java.io.IOException.

java.io.PrintStream for printable ASCII output

Here are the signatures of methods in the PrintStream class.

public class java.io.PrintStream extends java.io.FilterOutputStream {
    public PrintStream(java.io.OutputStream);
    public PrintStream(String filename);          // new, improved
    public void print(boolean);
    public void print(char);
    public void print(int);
    public void print(long);
    public void print(float);
    public void print(double);
    public void print(char[]);
    public void print(java.lang.String);
    public void print(java.lang.Object);

    public PrintStream printf (String, Object...);   // ... means variable-arity

    public void println();
    public void println(boolean);
    // and so on for all the primitives ...

    public void flush();
    public void close();
    public boolean checkError();
    public void write(int);
    public void write(byte[], int, int);
}

Using PrintStream we can write the code to complete example 2, and output the parts to a file. The whole thing will look like this:

// output <Integer, String> data from a Map to a File

try {
   Map<Integer, String> partsBook = new HashMap<Integer, String>();
   partsBook.put(45678, "prop wash, 1 bucket"); // put some data

   PrintStream ps = new PrintStream(
                          new FileOutputStream("partsBk.txt") );

// get the map set  and iterate over it
Set<Map.Entry<Integer, String>> set = partsBook.entrySet();
for (Map.Entry me : set)
    ps.printf( "%s %s  ", me.getKey(), me.getValue() );

} catch (IOException iox) {
   System.err.println("Excpn: " + iox);
}

image

The JDK 1.5 release introduced another constructor into class java.io.Printstream that lets you provide a filename argument directly:

PrintStream ps = new PrintStream( "partsBook.txt" );

The library will then create the necessary bottom-layer OutputStream class automatically behind the scenes for you! PrintWriter has been similarly updated. You still need to know about layering, because this only works when the destination is a File, not for any of the other output sinks. Interestingly, you don't get this for a DataOutputStream. Java I/O is a riddle inside a puzzle wrapped up in used bubble gum.

Methods of java.io.PrintWriter for 16-bit character output

Here are the signatures of methods in the PrintWriter class.

public class java.io.PrintWriter extends java.io.Writer {
    public PrintWriter(java.io.Writer);
    public PrintWriter(java.io.Writer,boolean);
    public PrintWriter(java.io.OutputStream);
    public PrintWriter(String filename);          // new, improved
    public PrintWriter(java.io.OutputStream,boolean);
    public void flush();
    public void close();
    public boolean checkError();

    <a id="page_404/">public void print(boolean);
    public void print(char);
    public void print(int);
    public void print(long);
    public void print(float);
    public void print(double);
    public void print(char[]);
    public void print(java.lang.String);
    public void print(java.lang.Object);

   public void println();
     // there are also println versions of all the above print methods, e.g.
   public void println(boolean);   // and so on

   public PrintWriter printf(String, Object... );  // the new printf

   public void write(int);
   public void write(char[]);
   public void write(char[], int from, int to);
   public void write(java.lang.String);
   public void write(java.lang.String, int from, int to);
}

There is a print() method for most primitive types, and also a println() method that follows the output with the end of line sequence for that platform. There is a printf. There is no print(byte) because byte-oriented output is done with an OutputStream, not a Writer. The print() methods in PrintWriter don't throw IOException. The append() and printf() methods can throw IOException, and the javadoc is wrong to claim they can't (I filed the bug with the Java team). You can call the method checkError() to see if an I/O error occurred at some earlier point. Using PrintStream we can write the code to complete example 3, and output to a pipe between two threads. The whole thing will look like the following example.

 // output Strings through a pipe to another thread
try {
   PipedWriter piw = new PipedWriter();
   PrintWriter pw = new PrintWriter( piw );
   final PipedReader pir = new PipedReader(piw);

  Thread t = new Thread () {   // anon inner class
      public void run() {
          System.out.println("thread 2");
          Scanner sc = Scanner.create(pir);
          while (sc.hasNext()) System.out.println( sc.next() );
      }
  };
  System.out.println("thread 1");
  t.start();
  pw.print("data through the pipe");
  pw.close();
} catch (Exception e) { }

When compiled and run, the output will be:

thread 1
thread 2
data
through
the
pipe

The code in italics is a second thread that reads tokens from the pipe using a scanner. Those tokens are piped from one thread to another in a thread-safe way.

We should conclude this description of PrintWriter by pointing out that, when writing to a file, the Java run-time system will convert 16-bit Character data into whatever the native encoding of the operating system is. If you have an 8-bit character set encoding as used in Western Europe and the US, then the high-order byte is dropped. For ISO 8859-1 characters, the dropped byte is 0 and makes no difference. You run into trouble if you are using characters where the high-order byte is used. Then your character data will be corrupted on output. This is why you shouldn't use a Stream when your data contains 16-bit characters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.168.203