Wrapping Additional Output Classes

As Figure 17-3 suggests, the java.io package has more classes that you can wrap around a Writer or OutputStream. The additional Writer wrapping classes can do things like filter the text as it goes by, or buffer it for performance. The additional OutputStream wrapping classes can bridge to a Writer class, filter, buffer, encrypt, and/or compress that data! When you wrap classes, only write from the outermost one. Otherwise, your I/O may get scrambled due to internal buffering.

Figure 17-3. Wrapping more writer classes

image

All these additional wrappers go between what we have termed the top layer classes and the bottom layer classes. It makes sense—the bottom layer (like FileWriter) is the ultimate destination and nothing can come below that. The top-layer class has the methods that pour the bits into the stack, so nothing can come above that. Additional wrappers go between the two end points.

We'll present two examples here. First, we'll show an example of using a FilterWriter to modify text as it gets written. We'll also show how a BufferedWriter is used in this example. The second example will use OutputStream 8-bit wrappers to create a zip file archive. If you haven't seen this before, you may be surprised at the small amount of code that achieves these great results.

Additional writer wrappers

FilterWriter is an abstract class for you to extend and override. It provides the opportunity to look at and modify 16-bit characters as they are output. You could do the same thing by extending many of the other writer classes, but using this class makes your purpose explicit.

Here is an example program that post-processes the stream written into it and changes all “1”s to “2”s. A Filter can do other things like count lines, correct spelling mistakes, calculate checksums, or write an encrypted or compressed stream.

A Filter to replace chars

In this example, the Filter overrides just two of the write() methods, but you may need to override any or all of the FilterWriter methods depending on which of them may be called by your code.

import java.io.*;
class MyFilter extends java.io.FilterWriter {
        public MyFilter(Writer w) { super(w); }

        public void write(String s, int off, int len) throws IOException {
                s = s.replace('1', '2'),
                super.write(s, off, len);
        }

        public void write(char[] cbuf, int off, int len) throws IOException {
                String s= new String(cbuf);
                this.write(s, off, len);
        }
}

A class that uses a Filter

You can decorate a FileWriter (or other destination class from Table 17-4) with a BufferedWriter, if you want to improve performance. FileWriter by itself sends its output to the underlying stream as it receives it. Because of disk latency and system call context switch overhead, it's always much quicker to do one 512-character transfer than 512 individual one-character transfers.

import java.io.*;
public class Example2 {
       public static void main(String args[]) {
                FileWriter myFW = null;
                try {
                         myFW = new FileWriter( "dogs.txt" );
                } catch(IOException x) { System.err.println("IOExcpn: " + x); }
                PrintWriter myPW = new PrintWriter(
                                                     new BufferedWriter(
                                                       new MyFilter( myFW ) ) );
                myPW.println("101 Dalmatians");
                myPW.close();
        }
}

Wrapping a BufferedWriter around any Writer will achieve that efficiency by batching smaller writes until its internal buffer is full. The only place you don't want buffered I/O is with interactive I/O. Buffered I/O should have been the default behavior in the I/O package.

The API permits the BufferedWriter and the FilterWriter to be wrapped in either order. But to get the most performance benefit, you want as much of the pipeline as possible operating on buffers of data. So put the BufferedWriter as near to the start of the pipeline as possible (i.e., the BufferedWriter should be the outermost or next-outermost wrapper). This ensures that all wrapper objects downstream of the BufferedWriter are working with buffered data.

If you run the program and look at the output file dogs.txt, you will see the output has been filtered. It now contains “202 Dalmatians”.

Output Stream wrappers

In the section above, we saw how a Writer could have a BufferedWriter and/or a subclass of FilterWriter interposed between the FileWriter (or other destination) and the PrintWriter. Output Streams can be wrapped in the same way to provide more functionality. You can wrap any or all of these output streams onto your original OutputStream:

  • BufferedOutputStream

  • Your subclass of FilterOutputStream

  • OutputStreamWriter

  • java.util.zip.ZipOutputStream

  • java.util.zip.GZIPOutputStream

  • java.util.jar.JarOutputStream

  • javax.crypto.CipherOutputStream

  • java.io.ObjectOutputStream

  • Others in the release, and those which you write yourself.

The OutputStreamWriter class converts an OutputStream class to a Writer class, allowing you to layer any of the Writer classes on top of that. It provides a bridge from the 8-bit byte world to the 16-bit character world. The main motivation for doing so is that you can also specify the character set when you construct an OutputStreamWriter. Please review Chapter 18 for more information on character sets. Most of your programs won't need to change the character set, but Java has a solid way to do it when you need to.

The CipherOutputStream will encrypt the stream that it gets and write the encrypted bytes. You have to set it up with a Cipher object (and a key). There is (a little) more detail in the online API documentation.

The zip, gzip, and jar output streams will compress the bytes written into them using the zip, gzip, and jar algorithms, respectively. Jar format is identical to zip format, but with the addition of a manifest file listing the names of other files in the archive. (I use a command like “jar -xvf foo.jar” on Windows instead of fooling about with WinZip, but this won't work on encrypted files now that PKZip and WinZip have implemented incompatible encryption schemes). An example of writing an archive of several files in zip format is coming up soon.

The ObjectOutputStream class allows you to save an object and all the objects it references. You can wrap this class around any of the other output streams and send the object to a file, to a socket, down a pipe, etc. Chapter 18 shows an example of ObjectOutputStream in use.

Example of outputting a zip file

Zip is a multifile archive format popularized by the PC but available on almost all systems now. The zip format offers two principal benefits: it can bundle several files into one file, and it can compress the data as it writes it to the zip archive. It's more convenient to pass around one file than 20 separate files. Compressed files are faster and cheaper to download or e-mail than their uncompressed versions. Java Archives (.jar) files are in zip format. Support for zip and gzip files was introduced with JDK 1.1.

Gzip, an alternative to zip widely used on Unix, uses a different format for the data and can only hold one file (not a series of them). You are supposed to use the tar utility to aggregate all your files into one file, then use gzip to shrink it. Sometimes the Unix philosophy of “a different tool for everything” is not the most convenient approach.

Java has classes that will compress and expand files into either the gzip or the zip format. If you wrote a file out in zip format, you have to read it back in that way too. The same holds for gzip format. The formats are not interchangeable. If you have a choice, opt for zip over gzip because it does more and is much more widely used.

Files aren't the only possible destination for zip streams (or any output, compressed or otherwise). You can equally easily send streams through a socket to another computer across the Internet, put them in a String or byte array for later retrieval, or send them through a pipe to another thread.

The following is an example program showing how three files can be put into a zip archive. You'll need three java source files to use as test data. Just make three copies of this file and give them the names shown in the code. After running this program, compare the size of the zip archive with the sum of the sizes of the three files. Text strings compress well, binary data less so.

import java.io.*;
import java.util.zip.*;
public class Example4 {

      // writing a zip archive
      static ZipOutputStream myZOS;

      public static void main(String args[]) throws IOException {
               myZOS = new ZipOutputStream (
                                        new BufferedOutputStream (
                                                new FileOutputStream("code.zip") ) );

               writeOneFile("Example1.java");
               writeOneFile("Example2.java");
               writeOneFile("Example3.java");
               myZOS.close();
      }

      static void writeOneFile(String name) throws IOException {
               ZipEntry myZE = new ZipEntry(name);
               myZOS.putNextEntry(myZE);

               BufferedInputStream myBIS = new BufferedInputStream(
                                                          new FileInputStream(name) );
               int c;
               while((c = myBIS.read()) != -1)     // read bytes until EOF
                        myZOS.write(c);                  // write the char we just read
               myBIS.close();       }
}

Executing this program will create a zip archive called code.zip. Each file in a zip archive is represented by an object called a ZipEntry. You can unpack it and recover the original source files with class ZIPInputStream, or use any of the standard zip tools like WinZip or Java's jar command.

That concludes output. Next, we'll look at Java's classes for input.

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

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