View RSS Feed

Java IO

How to work zip files in Java

Rating: 2 votes, 4.50 average.
by , 02-29-2012 at 08:59 AM (23898 Views)
he Java platform provides powerful API to work with the most popular compressed file format Ė zip format. This article will show you how to write code to compress and decompress zip files using Java. And beyond the basics, the code that works with multiple files in a directory recursively is also provided.

The Zip API

The package java.util.zip provides classes for working with zip file format. Although the documentation states that the package also provides classes for working with gzip file format, this article is focusing on working with zip format only because it is the most popular and widely used format.

The key classes in java.util.zip package are listed as following:

  • ZipInputStream: this class allows reading a file in zip format. It accepts an InputStream object in the constructor ZipInputStream(InputStream in); The getNextEntry() method allows iterating over the entries in the zip file; and the closeEntry() method should be invoked to close the current entry before reading the next one. The method read(byte[] b, int off, int len) reads an array of bytes from the current zip entry.

  • ZipOutputStream: this class allows writing files into a zip file. It accepts an OutputStream object in the constructor ZipOutputStream(OutputStream out); The putNextEntry() method allows to append a file at the end of the zip file. The compression level can be set via the method setLevel(int level), and the compression method can be set via the method setMethod(int method). There are two method of compression:


    • DEFLATED: The data files are compressed at the level specified.
    • STORED: The data files are not compressed, the zip file stores the files as an archive only.



    The method write(byte[] b, int off, int len) method writes an array of bytes to the current zip entry.

  • ZipEntry: this class represents an entry in a zip file and provides methods for reading and setting properties of a zip entry such as name, size, modification time, commentÖ

  • ZipFile: this class allows reading entries from a zip file. It is suitable for enumerating the entries only.



Compress files into a single zip file

First, we have to create an instance of the ZipOutputStream class, pass a FileOutputStream as its constructorís argument:

Java Code: Create a new ZipOutputStream object
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destZipFile));
And the following steps for adding a file into a zip file:
  • Put a next entry to the ZipOutputStream.
  • Construct a BufferedInputStream object to read bytes from the input file.
  • Use the BufferedInputStreamís read(byte[]) method to read an array of bytes from the input file, then write the bytes read to the ZipOutputStream using its write() method.
  • Repeat the above step until the end of the input file is reached.
  • Close the current zip entry.




The method addFileToZip() below implements the strategy above:


Java Code: Code of the method addFileToZip()
    private void addFileToZip(File file, ZipOutputStream zos)
            throws FileNotFoundException, IOException {
        zos.putNextEntry(new ZipEntry(file.getName()));

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                file));

        long bytesRead = 0;
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;

        while ((read = bis.read(bytesIn)) != -1) {
            zos.write(bytesIn, 0, read);
            bytesRead += read;
        }

        zos.closeEntry();
    }
The above method is working with a single data file only, but it is the core method on which more complex things are based. The following method, addFolderToZip(), will put all files under the current directory and sub directories, recursively, into the ZipOutputStream:


Java Code: Code of the method addFolderToZip()
    private void addFolderToZip(File folder, String parentFolder,
            ZipOutputStream zos) throws FileNotFoundException, IOException {
        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                addFolderToZip(file, parentFolder + "/" + file.getName(), zos);
                continue;
            }

            zos.putNextEntry(new ZipEntry(parentFolder + "/" + file.getName()));

            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));

            long bytesRead = 0;
            byte[] bytesIn = new byte[BUFFER_SIZE];
            int read = 0;

            while ((read = bis.read(bytesIn)) != -1) {
                zos.write(bytesIn, 0, read);
                bytesRead += read;
            }

            zos.closeEntry();

        }
    }
There are two noteworthy points about the above method:
  • Itís important to construct the path correctly to make the directory structure as the input one. This is done by passing the parentFolder to each method call.
  • The method is recursive, it calls itself if the current file is a directory.



And the following method finally glues all the pieces together, it accepts two arguments:

  • listFiles: A list of directory to be added to a zip file.
  • destZipFile: The path for destination zip file.



Code of the method:

Java Code: Code of the compressFiles() method
   public void compressFiles(List<File> listFiles, String destZipFile) throws FileNotFoundException, IOException {
       
       ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destZipFile));
       
       for (File file : listFiles) {
           if (file.isDirectory()) {
               addFolderToZip(file, file.getName(), zos);
           } else {
               addFileToZip(file, zos);
           }
       }
       
       zos.flush();
       zos.close();
   }
And finally we create a utility class that provides zip functionality:

Java Code: Code of the ZipUtil class
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* This utility compresses a list of files to standard ZIP format file.
* It is able to compresses all sub files and sub directories, recursively.
* @author Ha Minh Nam
*
*/
public class ZipUtil {
    /**
     * A constants for buffer size used to read/write data
     */
    private static final int BUFFER_SIZE = 4096;
   
    /**
     * Compresses a collection of files to a destination zip file
     * @param listFiles A collection of files and directories
     * @param destZipFile The path of the destination zip file
     * @throws FileNotFoundException
     * @throws IOException
     */
   public void compressFiles(List<File> listFiles, String destZipFile) throws FileNotFoundException, IOException {
       
       ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destZipFile));
       
       for (File file : listFiles) {
           if (file.isDirectory()) {
               addFolderToZip(file, file.getName(), zos);
           } else {
               addFileToZip(file, zos);
           }
       }
       
       zos.flush();
       zos.close();
   }
   
   /**
    * Adds a directory to the current zip output stream
    * @param folder the directory to be  added
    * @param parentFolder the path of parent directory
    * @param zos the current zip output stream
    * @throws FileNotFoundException
    * @throws IOException
    */
    private void addFolderToZip(File folder, String parentFolder,
            ZipOutputStream zos) throws FileNotFoundException, IOException {
        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                addFolderToZip(file, parentFolder + "/" + file.getName(), zos);
                continue;
            }

            zos.putNextEntry(new ZipEntry(parentFolder + "/" + file.getName()));

            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));

            long bytesRead = 0;
            byte[] bytesIn = new byte[BUFFER_SIZE];
            int read = 0;

            while ((read = bis.read(bytesIn)) != -1) {
                zos.write(bytesIn, 0, read);
                bytesRead += read;
            }

            zos.closeEntry();

        }
    }

    /**
     * Adds a file to the current zip output stream
     * @param file the file to be added
     * @param zos the current zip output stream
     * @throws FileNotFoundException
     * @throws IOException
     */
    private void addFileToZip(File file, ZipOutputStream zos)
            throws FileNotFoundException, IOException {
        zos.putNextEntry(new ZipEntry(file.getName()));

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                file));

        long bytesRead = 0;
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;

        while ((read = bis.read(bytesIn)) != -1) {
            zos.write(bytesIn, 0, read);
            bytesRead += read;
        }

        zos.closeEntry();
    }
}
We also added an integer constant field BUFFER_SIZE which defines the size of the buffer when reading and writing bytes.


Decompress a zip file

The decompression involves working with ZipInputStream class and the zip entries. We create an instance of the ZipInputStream class, pass a FileInputStream as its constructorís argument:

Java Code: Create a new ZipInputStream object
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));

The algorithm to decompress a zip file is as follow:

  • Create the destination directory if it does not exist. Destination directory is where all files are extracted to.
  • Iterate over the entries in the zip file:
    • If the entry is a directory, create the directory.
    • If the entry is a file, extract the file.

  • Repeat the above steps until there is no entry to read. Since the information of the entries in a zip file is organized in as rows in database, one after another, we donít have to use any recursive method to create the directory structure.
  • Close the ZipInputStream.



Here is the code of the method that extracts a single file:

Java Code: Code of method extractFile()
    private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }   
        bos.close();       
    }
The method is quite simple: it accepts a ZipInputStream and a filePath as its arguments. It invokes read() method of the ZipInputStream to read out an array of bytes, then use a BufferedOutputStream to write the bytes to the destination file. When the reading and writing are completed, close the output stream.

The method extractFile() is used in this main method, decompressFile(), to decompress the file:

Java Code: Code of method decompressFile()
    public void decompressFile(String zipFilePath, String destDirectory) throws IOException {
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
       
        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
       
        ZipEntry entry = zipIn.getNextEntry();
       
        while (entry != null) {
            String filePath = destDirectory + File.separator + entry.getName();
            if (!entry.isDirectory()) {
                extractFile(zipIn, filePath);
            } else {
                File dir = new File(filePath);
                dir.mkdir();
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }
The working of the method decompressFile() has been explained previously. It accepts two arguments:

  • zipFilePath: The absolute path of the zip file to be decompressed.
  • destDirectory: the absolute path of the directory where all files in the zip file are extracted to.




And finally the UnzipUtil.java class put all pieces together:

Java Code: Code of UnzipUtil class
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
* This utility decompresses a standard zip file to a destination directory.
* @author Ha Minh Nam
*
*/
public class UnzipUtil {
    /**
     * A constants for buffer size used to read/write data
     */   
    private static final int BUFFER_SIZE = 4096;
   
    /**
     *
     * @param zipFilePath Path of the zip file to be extracted
     * @param destDirectory Path of the destination directory
     * @throws IOException IOException if any I/O error occurred
     */
    public void decompressFile(String zipFilePath, String destDirectory) throws IOException {
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
       
        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
       
        ZipEntry entry = zipIn.getNextEntry();
       
        while (entry != null) {
            String filePath = destDirectory + File.separator + entry.getName();
            if (!entry.isDirectory()) {
                extractFile(zipIn, filePath);
            } else {
                File dir = new File(filePath);
                dir.mkdir();
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }
   
    /**
     * Extracts a single file
     * @param zipIn the ZipInputStream
     * @param filePath Path of the destination file
     * @throws IOException if any I/O error occurred
     */
    private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }   
        bos.close();       
    }

}
The integer constant field BUFFER_SIZE is declared to specify the size of a byte array to be read in the buffer.


Testing the utility classes

So far we have created two utility classes for zip and unzip, itís time to write a test class. The code of the test class looks like as following:

Java Code: Code of test class
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class TestZipUtil {
    public static void main(String[] args) throws Exception {
        // test compress files
        ZipUtil zipper = new ZipUtil();
        File directoryToZip = new File("E:/CodeLib/Java/Swing");
        String zipFilePath = "E:/CodeLib/Swing.zip";
        List<File> listFiles = new ArrayList<File>(1);
        listFiles.add(directoryToZip);
       
        zipper.compressFiles(listFiles, zipFilePath);
       
        // test decompress a zip file       
        zipFilePath = "D:/Temp/Zip/CodeLib.zip";
        String destFilePath = "D:/Temp/UnZip";
        UnzipUtil unzipper = new UnzipUtil();
        unzipper.decompressFile(zipFilePath, destFilePath);
    }
}

Conclusion

With the utility classes have been written, we have a corner stone to work with zip files in Java. Did you notice that we didnít set the compression method and the compression level? Yes, because the default settings is appropriate in most cases. So if you want to explore further, dive into the API and experiment yourself.
Bob87 and davemac like this.

Submit "How to work zip files in Java" to Facebook Submit "How to work zip files in Java" to Digg Submit "How to work zip files in Java" to del.icio.us Submit "How to work zip files in Java" to StumbleUpon Submit "How to work zip files in Java" to Google

Tags: file, java, zip Add / Edit Tags
Categories
Zip

Comments