introductionDo you need the ability to add data compression to your enterprise application? Well, look no further because Java 1.1 provides a package for zip-compatible data compression. The new package, java.util.zip, allows software developers to read, create, modify and write PKZIP and GZIP compatible files.
This article will provide step-by-step examples for reading data from a ZIP file and writing data to a ZIP file. Also, the article will discuss the methods available for accessing property information for the compressed entries in a ZIP file.
The Benefits of Data Compression |
By using the java.util.zip package, you will be able to incorporate sophisticated compression technology into your Java applications with minimal work. The package not only provides single file compression but you can also create multi-file archives. Since the ZIP files created by the package adhere to the ZIP standard, you will be able to use these files with the PKZIP utility.
Sample Application Using Data Compression |
Smart E-mailer
You can use ZIP data compression to create a smart e-mail program. The e-mail program can be developed to have the following features:
- automatic compression of outgoing attachments
- automatic uncompression of incoming attachments
- compress old e-mail messages
Normally, users attach very large word-processing or spreadsheet files to e-mail message. The e-mail program assists the user with compressing the file. Before sending out the message the e-mail program checks to see if the attached files are in a compressed format. If not, then the e-mail program automatically compresses the file in ZIP format.
The e-mail program also automatically unzips incoming attachments. The user simply sets a program option for unzipping files in a specific directory. Also, in order to conserve disk space, an archive feature is available to compress old e-mail messages. This will allow the user to conserve valuable disk space.
The user will benefit because the e-mail program has ZIP data compression built-in. Also, the user no longer has to fumble with an external ZIP utiliy.
The Components of a ZIP file |
Before we get into the nuts-and-bolts of compressing and uncompressing data, we will discuss the components of a ZIP file. A ZIP file is composed of one or more compressed files. Refer to figure 1 for the ZIP file structure.
A compressed file is described by a zip entry. The zip entry contains information for the compressed file such as the file name, original size, compressed size and additional file details. In a later section, we will see how to access this information.
Uncompressing a ZIP file |
Uncompressing a ZIP file is simply a matter of reading data from an input stream. In this example, we will write the code fragments to unzip the contents of a file secrets.zip. The five step process follows:
Step 1: Get the ZIP input stream
The java.util.zip package provides a class ZipInputStream for sequentially reading ZIP files. The constructor for this class accepts an InputStream object. As you can see, a ZipInputStream is created similar to other input streams.
source = new ZipInputStream(fis);
while ( (theEntry = source.getNextEntry()) != null ) { // read the data // // more code to come }
After the getNextEntry() method is called, then you can begin reading the data associated with this ZipEntry.
Step 3: Prepare the uncompressed output stream
Now is a good time to setup the uncompressed output stream. This code is placed inside of the while-loop from step 2.
targetStream = new BufferedOutputStream(fos, DATA_BLOCK_SIZE);
Step 4: Inside of the main while-loop, you can read source zipped data and write it to the uncompressed stream.
while ((byteCount = source.read(data, 0, DATA_BLOCK_SIZE)) != -1) { targetStream.write(data, 0, byteCount); }
The read() method retrieves data from the source zipped input stream. This method will return the number of bytes read in. If end-of-file is reached, then the read() method will return a -1. For each block of data that is read in, it is then written to the uncompressed target stream.
Step 5: Close the input/output streams
After processing the data for each entry (ie reading and writing), the target stream is flushed and closed.
targetStream.close();
Compressing data to a ZIP file |
In order to compress data to a ZIP file, you can use the ZipOutputStream class. This class provides the functionality of writing the data in a compressed format. There is only a small amount of work required by the developer to create a ZIP file. The simple six step process is outlined below.
Step 1: Create the zip output stream
The java.util.zip package provides the class ZipOutputStream for writing ZIP files. The constructor for this class accepts an OutputStream object. Basically, you can pass the output stream of the file you are writing to. Here is an example of creating a ZIP file titled “modules.zip“:
fos = new FileOutputStream(“modules.zip”);
targetStream = new ZipOutputStream(fos);
targetStream.setMethod(ZipOutputStream.DEFLATED);
Notice the call to setMethod(). By passing the value of DEFLATED, the ZipOutputStream object will store the files in a compressed manner. By default, the compression method is set to ZipOutputStream.DEFLATED.
You also have the option of just storing the files in an uncompressed format. To accomplish this, you pass the value of ZipOutputStream.STORED to the setMethod() routine. However, when storing files in an uncompressed format, you must specify the CRC-32 checksum and the file size. Please reference the JDK 1.1 API for details on the CRC32 class.
You can also set the level of compression by calling the setLevel(int aLevel) method. The compression levels range from 1-9 with 1 being the weakest and 9 being the fastest level of compression.
Step 2: Open the source data file
Now that the target zip output stream is created, you can open the source data file. In this code example, the file “java_intro.ppt” is the source data file:
String dataFileName = “java_intro.ppt”;
fis = new FileInputStream(dataFileName);
sourceStream = new BufferedInputStream(fis);
Step 3: Create the zip entry
You will need to create a zip entry for each data file that is read. Recall from an earlier section that a zip entry contains file information such as the file name, original size, compressed size and additional details. Most of the zip entry information will be updated once the data is written to the zip output stream. Here is the code for creating a ZipEntry object:
theEntry = new ZipEntry(dataFileName);
Step 4: Put the zip entry into the archive
Before you can write information to the zip output stream, you must first put the zip entry object that was created in Step 3.
targetStream.putNextEntry(theEntry);
Step 5: Read source and write the data to the zip output stream
Finally, the coast is clear for you to read the source file and write the data. Since you are writing to a zip output stream, the data will be written in a compressed format without any additional work by you.
while ((bCnt = sourceStream.read(data, 0, DATA_BLOCK_SIZE)) != -1)
{
targetStream.write(data, 0, bCnt);
}
targetStream.close();
sourceStream.close();
Checking ZIP file properties |
At any time, you can find out the properties of a given ZIP file. Recall that the ZIP file is composed of zip entries for each compressed file. The ZipEntry class has a number of useful methods for accessing compressed file details (see figure 2).
Here is a description of the popular methods available in the ZipEntry class:
Method Signature | Description |
---|---|
public String getComments() | Returns any additional comments that given for this zip entry |
public long getCompressedSize() | Returns the compressed size of the file in bytes. |
public int getMethod() | Returns the compression method: DEFLATED (for compressed files), STORED (for uncompressed files). |
public String getName() | Returns the name associated with the entry. This is usually the file name. |
public long getSize() | Returns the original file size in bytes. You can use this method in conjunction with getCompressedSize() to find out compression percentage. |
public long getTime() | Returns the date / time stamp of the file. |
Here is a code sample that uses some of the above methods to display information for a given ZipEntry
System.out.print(theEntry.getCompressedSize() + “\t\t”);
System.out.println(theEntry.getName());
Compatibility Issues |
The java.util.zip package is compatible with PKZIP version 2.04G. However, I encountered problems when using other zip utilities. For example, earlier versions of WinZip could not extract ZIP files created with java.util.zip. This problem was solved when I downloaded the latest version of WinZip, version 6.3. If you are going to share your ZIP files with others then be sure to test for compatibility with the various zip utilities.
Conclusion |
As you can see, the java.util.zip package is easy to use. By following a simple six-step process, you can create, read and write ZIP files. You can easily tune the compression level of files to favor speed or size. The package allows you to take advantage of the many benefits of reading and writing ZIP files. Currently software companies are selling ZIP controls for Visual Basic and Delphi. I wonder who will ship the first JavaBean with ZIP functionality? Well, armed with the information in this article…it could easily be you!
import java.util.zip.*; import java.io.*; import java.util.*; // File: jkunzip.java // /** * * This class will allow you to perform basic pkzip compatible * data uncompression. <p> * * <p> * The basic steps are: <br> * 1. get the zipped input stream <br> * 2. get the zipped entries <br> * 3. prepare the uncompressed output stream <br> * 4. read source zipped data and write to uncompressed stream <br> * 5. close the source and target stream <br> * * <p> * Command syntax <br> * <pre> * Usage: java jkunzip [-v] zipfile <br> * where option includes: <br> * -v List zip file contents <br> * </pre> * * <p> * NOTE: This version expands entries w/ directory structures. * * @author Chad (shod) Darby, darby@j-nine.com * @version 3.13, 16 Sep 1999 * */ public class jkunzip { //------------------------------------------------- // DATA MEMBERS // protected String zipFileName; protected boolean showListingFlag = false; protected final int DATA_BLOCK_SIZE = 2048; //------------------------------------------------- // CONSTRUCTORS // /** * * The constructor is used to create a new jkunzip object based * on the command line arguments. <br> * * <p> * Command syntax <br> * <pre> * Usage: java jkunzip [-v] zipfile <br> * where option includes: <br> * -v List zip file contents <br> * </pre> * * @param args a string array of command line arguments. * */ public jkunzip(String args[]) { parseCommandLineArgs(args); if (zipFileName == null) { System.out.println(getUsageString()); System.exit(1); } } //------------------------------------------------- // METHODS // /** * Parses the command line arguments. <br> * * @param args command line args as an array of strings. * */ protected void parseCommandLineArgs(String args[]) { String name; int length = args.length; // check if empty arguments if (length == 0) { System.out.println(getUsageString()); System.exit(1); } if (length == 1) { if ( ! (args[0].equals("-v")) ) { zipFileName = args[0]; return; } else { System.out.println(getUsageString()); System.exit(1); } } else { zipFileName = args[1]; showListingFlag = true; } } /** * * Returns the usage string for this class. * * @return Returns the usage string. * */ protected String getUsageString() { String temp = null; temp = "\nJKUNZIP ver 1.13\n\n"; temp += "Usage: java jkunzip [-v] zipfile\n\n"; temp += "where option includes:\n"; temp += "-v\tList zip file contents\n"; return temp; } /** * * Lists the contents of the zipped file. <br> * * @param zipFileName name of the zipped file. * */ protected void listContents(String zipFileName) { ZipFile zf; ZipEntry theEntry; try { zf = new ZipFile(zipFileName); Enumeration entries = zf.entries(); // display the listing header System.out.println("Length\t\tSize\t\tName"); System.out.println("------\t\t----\t\t----"); // list the contents of each zipped entry while (entries.hasMoreElements()) { theEntry = (ZipEntry) entries.nextElement(); System.out.print(theEntry.getSize() + "\t\t"); System.out.print(theEntry.getCompressedSize() + "\t\t"); System.out.println(theEntry.getName()); } } catch (IOException exc) { exc.printStackTrace(); } } /** * * Performs the different switches for the jkunzip object. <br> * * Determines if it should list contents or extract files. <br> * */ public void unzipIt() { System.out.println("Searching ZIP: " + zipFileName + "\n"); if (showListingFlag) { listContents(zipFileName); } else { extractFiles(zipFileName); } } /** * * Extracts the files contained in the zipped file. <br> * * <p> * The basic steps are: <br> * 1. get the zipped input stream <br> * 2. get the zipped entries <br> * 3. prepare the uncompressed output stream <br> * 4. read source zipped data and write to uncompressed stream <br> * 5. close the source and target stream <br> * * @param theZipFleName name of the zipped archive * */ protected void extractFiles(String theZipFileName) { FileInputStream fis = null; ZipInputStream sourceZipStream; FileOutputStream fos = null; BufferedOutputStream targetStream; ZipEntry theEntry = null; String entryName = null; try { // 1. get the zipped input stream fis = new FileInputStream(theZipFileName); sourceZipStream = new ZipInputStream(fis); // 2. get the zipped entries while ( (theEntry = sourceZipStream.getNextEntry()) != null ) { entryName = theEntry.getName(); // 3. prepare the uncompressed output stream try { fos = new FileOutputStream(entryName); } catch (FileNotFoundException exc) { // the directory is not created...so let's build it! buildDirectory(entryName); fos = new FileOutputStream(entryName); } targetStream = new BufferedOutputStream(fos, DATA_BLOCK_SIZE); System.out.println("\tUnzipping: " + theEntry); int byteCount; byte data[] = new byte[DATA_BLOCK_SIZE]; // 4. read source zipped data and write to uncompressed stream while ( (byteCount = sourceZipStream.read(data, 0, DATA_BLOCK_SIZE)) != -1) { targetStream.write(data, 0, byteCount); } // 5. close the target stream targetStream.flush(); targetStream.close(); } // close the source stream sourceZipStream.close(); } catch (IOException exc) { exc.printStackTrace(); } } /** * Creates the directory structure */ protected void buildDirectory(String entryName) throws IOException { StringTokenizer st = new StringTokenizer(entryName, "/"); int levels = st.countTokens() - 1; StringBuffer directory = new StringBuffer(); File newDir; for (int i=0; i < levels; i++) { directory.append(st.nextToken() + "/"); } newDir = new File(directory.toString()); newDir.mkdirs(); } /** * The main driver routine! */ public static void main(String args[]) { jkunzip myApp = new jkunzip(args); myApp.unzipIt(); }
}
=================================================
import java.util.zip.*; import java.io.*; // File: jkzip.java // /** * * This class will allow you to perform basic pkzip compatible * data compression. <p> * * The basic steps are: <br> * 1. Create the zip output stream <br> * 2. Open source data file <br> * 3. Create the zip entry <br> * 4. Put the entry <br> * 5. Read source and write the data to the zip output stream <br> * 6. Close the zip entry and other open streams <br> * * <p> * Command syntax <br> * <pre> Usage: java jkzip zipfile [files] </pre> <br> * * <p> * NOTE: This version does not recurse sub-directories. * * @author Chad (shod) Darby, darby@j-nine.com * @version 1.13, 27 Sep 97 * * */ public class jkzip { //------------------------------------------------- // DATA MEMBERS // String archiveFileName; String fileNamesArray[]; //------------------------------------------------- // CONSTRUCTORS // /** * * The constructor is used to create a new jkzip object based * on the command line arguments. <br> * Usage: <pre> java jkzip zipfile [files] </pre> </b> <br> * * @param args - a string array of command line arguments. * */ public jkzip(String args[]) { if (args.length == 0) { System.out.println(getUsageString()); System.exit(1); } archiveFileName = args[0]; // create the fileNames array // // if user supplied list of file(s) then args.length // will be greater than 1. // if (args.length > 1) { fileNamesArray = new String[args.length - 1]; for (int j=0; j < fileNamesArray.length; j++) { fileNamesArray[j] = args[j+1]; } } // if user didn't supply files then zip the entire directory // else if (args.length == 1) { File currentDirectory = new File("."); fileNamesArray = currentDirectory.list(); } } //------------------------------------------------- // METHODS // /** * Reads the data source and writes the compressed file. <br> * * <p> * The basic steps are: <br> * 1. Create the zip output stream <br> * 2. Open source data file <br> * 3. Create the zip entry <br> * 4. Put the entry <br> * 5. Read source and write the data to the zip output stream <br> * 6. Close the zip entry and other open streams <br> */ public void zipIt() { BufferedInputStream sourceStream; File theFile; FileInputStream fis; ZipOutputStream targetStream; FileOutputStream fos; ZipEntry theEntry; final int DATA_BLOCK_SIZE = 2048; int byteCount; byte data[]; try { // 1. create the ZipOutputStream // System.out.println("Creating ZIP: " + archiveFileName); fos = new FileOutputStream(archiveFileName); targetStream = new ZipOutputStream(fos); targetStream.setMethod(ZipOutputStream.DEFLATED); // loop thru the array for (int i=0; i < fileNamesArray.length; i++) { theFile = new File(fileNamesArray[i]); // check if file is a directory, if so then skip if (theFile.isDirectory()) { // i won't recurse directories so let's skip System.out.println("\tSkipping directory: " + theFile); continue; } // 2. open source file // fis = new FileInputStream(fileNamesArray[i]); sourceStream = new BufferedInputStream(fis); // 3. create the ZipEntry // theEntry = new ZipEntry(fileNamesArray[i]); System.out.print("\tAdding: " + theEntry.getName()); // 4. put the entry // targetStream.putNextEntry(theEntry); // 5. read source data and write target data // to compressed output stream // data = new byte[DATA_BLOCK_SIZE]; while ( (byteCount = sourceStream.read(data, 0, DATA_BLOCK_SIZE)) != -1) { targetStream.write(data, 0, byteCount); } targetStream.flush(); // 6. close the entry // System.out.println(", done."); targetStream.closeEntry(); sourceStream.close(); } // end for loop targetStream.close(); } catch (IOException e) { e.printStackTrace(); } } /** * * Returns the usage string for this class. * * @return Returns the usage string. * */ public String getUsageString() { String temp = null; temp = "\nJKZIP ver 1.13\n\n"; temp += "Usage: java jkzip zipfile [files]\n"; return temp; } public static void main(String args[]) { jkzip myApp = new jkzip(args); myApp.zipIt(); } }