Channels ▼
RSS

JVM Languages

Java and the Zip File Format

Source Code Accompanies This Article. Download It Now.


Dr. Dobb's Journal December 1997: Java and the Zip File Format

Use Java to compress and extract files

Mark is the author of The C++ Programmer's Guide to the Standard Template Library (IDG Books, 1995) and The Data Compression Book, Second Edition (M&T Books, 1995), and is a partner in Addisoft Consulting (http://www.addisoft.com/). He can be contacted at markn@tiny.com.


Java Archive (JAR) files, which were released with the JDK 1.1, are compressed archives that contain various components of a Java applet -- class files, images, sound clips, and the like. Ideally, the JAR format should speed up the loading of applet components across the Web by compressing data and reducing the number of separate transactions required during the download process.

Sun's choice of the Zip file format for JAR files has a couple of advantages. To begin with, commercial and free Zip tools are widely available, including zlib and InfoZip products. Equally as important, Zip is an open format, whereas ActiveX technology requires use of the proprietary CAB format for compression and archiving.

To implement support for JAR files, Sun first ported much of zlib to pure Java, which took care of implementing the deflate compression algorithm used in PKZip 2.x. Sun then created a fairly thin set of wrapper classes that produce the archive structure around the deflated data. The result is the java.util.zip package. In this article, I'll examine how to use this package, and avoid some common mistakes the library overlooks.

The Zip Format

Figure 1 shows the layout of the Zip format. You can think of a Zip file as a stream-oriented format, which means you can create an archive by writing sequentially without ever having to seek backwards in the output file.

The Zip file starts with a sequence of files, each of which can be compressed or stored in raw format. Each file contains a local header (placed immediately before its data) that contains most of the information about the file, including time stamps, compression method, and filename. The compressed file contents immediately follow and are terminated by an optional data descriptor. The data descriptor contains the file's CRC and compressed size, which frequently are not available when writing the local file header. (If they are, the data descriptor can be skipped.)

Each file in the archive is laid down sequentially in this format, followed by a central directory at the end of the Zip archive. The central directory is a contiguous set of directory entries, each of which contains all the information in the local file header, plus extras such as file comments and attributes. Most importantly, the central directory contains pointers to the position of each file in the archive, which makes navigation of the Zip file quick and easy.

Key Classes

The complete java.util.zip package includes 14 classes, one interface, and two exception classes. While this may sound like a lot, most users will be able to skip over the bulk of the package. If you concentrate on the four core classes of the package, you can perform the three most-common operations on Zip files: creation, extraction, and directory reads. The four core classes are:

  • ZipEntry, which contains all the information needed to write both the local file header and the central directory record for a specific file, such as file sizes, time stamps, comments, attributes, and so on.
  • ZipFile, which is used only when reading from Zip files. It provides access to the entire central directory for the Zip file, and produces ZipEntry objects on demand for any file in the archive.
  • ZipInputStream, which is an input stream that behaves like a standard InputStream, but works on deflated streams. Calls to the read() method on this stream will decompress bytes on the fly.
  • ZipOutputStream, which is an output stream that looks like OutputStream, but deflates data transparently to the caller. Calls to write() methods will write data in the deflated format expected by the Zip standard.

Putting the Classes to Work

Listing One is ZipList, a program that prints out the entire contents of a Zip file, including formatted date and time stamps. To see the contents of foo.zip, ZipList.class can be executed using: java ZipList foo.zip. Figure 2 is the output for a sample Zip file that ZipList produces.

The listing is simple. First, create a new ZipFile object by calling the standard constructor with a filename: ZipFile z=new ZipFile(args[0]);. Once the ZipFile object has been created, call the entries() method, which returns an Enumeration object. The Enumeration object successively returns ZipEntry objects, one for each file in the Zip file. The methods in the ZipEntry object used to read various attributes include: String getComment(), long getCompressedSize(), long getCrc(), byte[] getExtra(), int getMethod(), String getName(), long getSize(), long getTime(), and boolean isDirectory().

Extracting Files

ZipExtract.java (available electronically; see "Availability," page 3) is a GUI program that lets you enter a Zip filename in a simple text box, then list the Zip file contents in a list box. You can then select files and extract them in a batch process; see Figure 3.

The method used to load up the list box in ZipExtract is a stripped-down variation of the code in ZipList. Since the list box only contains the filename, the enumeration loop only has to call ZipEntry.getName().

Most of ZipExtract is found in the two methods that are called when the "Extract Files" button is pressed. The extractFiles() method consists of a loop that is run through once per selected filename in the list. The routine calls ZipFile.getEntry() for each file to get the ZipEntry object associated with the given filename.

The real file extraction/insertion work in java.util.zip is your responsibility. The ZipFile object provides you with a ZipInputStream object (upcast to InputStream) via the getInputStream() method, and the rest is up to you. You read bytes from the stream (which transparently decompresses), and write the output wherever you wish. I do this in the ZipExtract sample program in method extractOneFile(). I read in 100 KB at a time, and write them to the specified output file. Since the extraction process from a ZipFile is exceptionally fast, I can supply a progress update only once every 100 KB and still seem fairly responsive.

An examination of the code in ZipExtract.java reveals a shortcoming of the java.util.zip package. When extracting files using PKUNZIP.EXE or unzip.exe, you'd expect file time stamps and protection bits to be set to the values stored in the Zip file. This doesn't happen in ZipExtract. Java.util.zip ducked its responsibilities in this area by not providing any sort of extract() method. Worse yet, the entire Java library leaves out the functions needed to do this on your own. If you want to set time stamps or protection bits from Java, you are going to have to resort to native methods, a decidedly unpleasant proposition.

Creating Zip Archives

ZipCreate.java (available electronically) is a command-line program called with a Zip filename as a command-line argument, followed by a list of wild-card file specifications. ZipCreate expands the wild-card file specs into a list of files, removes any duplicates, and creates a Zip file with the resulting list. All this is done with Zipper, a worker class I created.

Oddly enough, creating a Zip archive doesn't involve the ZipFile class. The Zipper.create() method (also available electronically) shows the process, which is fairly simple. A new ZipOutputStream object is created using a standard filename. Files are added to the output stream one at a time, using a ZipEntry object to control the process. The data preceding the file in the Zip archive is written using method ZipOutputStream.putNextEntry(). The file data is then written using a series of standard write() calls (which compress transparently). Once again, the lack of a standard insert() function means you have to do all the hard work yourself. After all of the file data is written, a call to ZipOutputStream.closeEntry() writes the data that follows the compressed file.

Zipper.create() highlights a few blank spots in the Java library. It would be nice to be able to use the Java API to expand wild cards. The File.list() method in Java provides the hooks to do just that, but the lack of a regular expression parser means that third-party solutions are required for implementation. I used an excellent package called pat 1.1 by Steve R. Brandt (http://www.win.net/~stevesoft/pat/), which plugged quickly and easily into my application, requiring only a small amount of code.

Another oversight in the Java API is in the ZipEntry class definition. Although ZipEntry is used to carry around information about a file, such as its length, time stamps, and protection bits, none of this data is created automatically! If you want your Zip file to contain accurate time stamps and protection settings, you are going to have to enter them yourself.

Exceptions

Up to this point, I've only glossed over the topic of errors. Of course, one of the really nice things about Java is that you can get away with a casual treatment of errors. All of my programs feature a try/catch block at a high level, which means any fatal errors thrown by java.util.zip will be caught and printed out when they occur. Rather than having to check flags and status bits after every library call, I can proceed as if everything works perfectly, knowing that errors will be caught somewhere else, if and when they occur.

When I compare this sort of error handling to the kind I needed to use when writing demo programs for my C++ Zip library at Greenleaf Software, it's easy to see why Java is so popular. Handling errors at a high level via exceptions makes the rest of your code quite a bit easier to read, write, and maintain.

Conclusion

Utilities like java.util.zip will help convince people that Java is more than just a toy language for demo applets on the Web. On the downside, java.util.zip is currently pretty shallow. Sun's classes don't check for the validity of things such as filenames, extra data, time stamps, and so on. Nor does Sun provide support for low-level file-attribute manipulation. Finally, it would be a good idea to make the java.util.zip friendlier by adding functions to actually perform the insertion and extraction of files.


Listing One

// ZipList.java - A Tiny Software (tm) Project// Mark Nelson
// markn@tiny.com
// http://web2.airmail.net/markn
// Command-line Java app that provides a brief listing of the contents
// of a Zip file. The ZipFile class makes this easy by providing an 
// Enumeration object that can be used to step through all the entries in the 
// Zip file. The ZipEntry object returned by the enumeration is used to get
// the file name, size, compressed size, and timestamps.
// To compile:  javac ZipList.java
// To run: java ZipList zip-name
// Requires JDK 1.1
//
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.text.*;


</p>
public class ZipList
{
    static public void main( String[] args )
    {
        if ( args.length < 1 ) {
            System.out.println( "Usage: ZipList zipfile" );
            return;
        }
     // Printing the times and dates require some work beforehand. The two
     // formatting objects are what do the job inside the main loop.
     DateFormat df= DateFormat.getDateInstance();
     DateFormat tf= DateFormat.getTimeInstance();
     tf.setTimeZone( TimeZone.getDefault() );
     //
     try {
         ZipFile z = new ZipFile( args[ 0 ] );
         System.out.println( "Listing of : " + z.getName() );
         System.out.println( "" );
         System.out.println( "Raw Size    Size     Date    Time         Name" );
         System.out.println( "--------  -------  -------  -------   --------" );
         Enumeration enum = z.entries();
         while ( enum.hasMoreElements() ) {
             ZipEntry e = (ZipEntry) enum.nextElement();
             Date d = new Date( e.getTime() );
             System.out.print( format( e.getSize(), 9 ) + " " );
             System.out.print( format( e.getCompressedSize(), 9 ) + " " );
             System.out.print( " " + df.format( d ) + " " );
             System.out.print( " " + tf.format( d ) + " " );
             System.out.println( " " + e.getName() );
         }
     }
     catch (IOException ioe ) {
         System.out.println( "exception: " + ioe );
         ioe.printStackTrace();
     }
 }
 // Used to print a long integer using a specific width.
 static String format( long l, int width )
 {
     String s = new Long( l ).toString();
     while ( s.length() < width )
         s += " ";
     return s;
 }
}

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video