杰瑞科技汇

Java ZipOutputStream如何高效压缩多文件?

Of course! The java.util.zip.ZipOutputStream class in Java is a powerful tool for writing data to ZIP archives. It's part of the standard Java Development Kit (JDK) and works by wrapping other output streams, allowing you to write compressed files and directories into a single .zip file.

Java ZipOutputStream如何高效压缩多文件?-图1
(图片来源网络,侵删)

Here's a comprehensive guide covering everything from the basics to advanced usage and best practices.


Core Concepts

  • OutputStream: At its heart, ZipOutputStream is an OutputStream. You write data to it, and it handles the compression and formatting of the ZIP file.
  • ZipEntry: Before you can write data for a file or directory inside the ZIP archive, you must create a ZipEntry object. This object acts as a placeholder or a "header" for the file/directory and contains metadata like its name, size, compression method, and modification time.
  • Stream-Based Operation: The process is sequential. You create a ZipEntry, tell the ZipOutputStream you're starting that entry, write the file's content, and then tell the ZipOutputStream you're finished with that entry. You repeat this for every file you want to add.

Basic Example: Creating a Simple ZIP File

This is the most common use case: adding a single file to a new ZIP archive.

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.ZipOutputStream;
public class SimpleZipExample {
    public static void main(String[] args) {
        String sourceFile = "testfile.txt";
        String zipFile = "archive.zip";
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            // 1. Create a ZipEntry for the file
            ZipEntry zipEntry = new ZipEntry(sourceFile);
            zos.putNextEntry(zipEntry);
            // 2. Read the source file and write its content to the ZIP output stream
            FileInputStream fis = new FileInputStream(sourceFile);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) > 0) {
                zos.write(buffer, 0, len);
            }
            // 3. Close the ZipEntry
            zos.closeEntry();
            fis.close();
            System.out.println("Successfully created " + zipFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Breakdown of the Code:

  1. try-with-resources: This is the modern, recommended way to handle streams. It ensures that FileOutputStream and ZipOutputStream are automatically closed, even if an exception occurs.
  2. new ZipOutputStream(fos): We wrap the FileOutputStream (which writes to archive.zip) with a ZipOutputStream. This gives us the ability to write ZIP-formatted data.
  3. new ZipEntry(sourceFile): We create an entry for the file inside the ZIP. The name of the entry ("testfile.txt") will be the name of the file as it appears inside the archive.
  4. zos.putNextEntry(zipEntry): This is a crucial step. It tells the ZipOutputStream that we are about to start writing the data for the zipEntry. The stream's internal state is prepared for this new entry.
  5. Reading and Writing: We read data from the source file (testfile.txt) in chunks (using a buffer) and write it directly to the zos. The zos automatically compresses this data.
  6. zos.closeEntry(): This signals that we have finished writing all the data for the current entry. It writes the necessary footer information for that entry in the ZIP file.

Advanced Example: Zipping a Directory Recursively

A more practical example is zipping an entire directory and all its subdirectories and files.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class DirectoryZipExample {
    public static void main(String[] args) {
        String sourceFolder = "my_folder";
        String zipFile = "my_folder_archive.zip";
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            Path sourcePath = Paths.get(sourceFolder);
            zipFile(sourcePath, sourcePath, zos);
            System.out.println("Successfully created " + zipFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * Recursively zips a directory or a file.
     * @param source The root source folder.
     * @param path The current file or directory to be zipped.
     * @param zos The ZipOutputStream to write to.
     * @throws IOException
     */
    private static void zipFile(Path source, Path path, ZipOutputStream zos) throws IOException {
        File file = path.toFile();
        // If it's a directory, create a directory entry and recurse
        if (file.isDirectory()) {
            // Add directory entry (optional, but good practice)
            // The name is relative to the source folder
            String dirName = source.relativize(path).toString() + File.separator;
            zos.putNextEntry(new ZipEntry(dirName));
            zos.closeEntry();
            for (File nestedFile : file.listFiles()) {
                zipFile(source, nestedFile.toPath(), zos);
            }
        } else {
            // If it's a file, create a file entry and write its content
            String entryName = source.relativize(path).toString();
            zos.putNextEntry(new ZipEntry(entryName));
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = fis.read(buffer)) > 0) {
                    zos.write(buffer, 0, len);
                }
            }
            zos.closeEntry();
        }
    }
}

Key Differences from the Simple Example:

  • Recursive Logic: The zipFile method checks if the current Path is a directory or a file.
  • Path.relativize(): This is the magic for getting the correct internal path of a file/directory within the ZIP. It calculates the path of path relative to source. For example, if source is my_folder and path is my_folder/src/main/java, relativize will return src/main/java.
  • Directory Entries: We explicitly add a ZipEntry for directories, ending with a (or File.separator). This preserves the directory structure inside the ZIP file.

Important Methods and Best Practices

Setting Compression Level

You can control the trade-off between speed and compression ratio.

Java ZipOutputStream如何高效压缩多文件?-图2
(图片来源网络,侵删)
// Default compression (DEFLATED, level 6)
ZipOutputStream zos = new ZipOutputStream(fos);
// Set compression level (0-9)
// 0: No compression (store only)
// 1: Best speed
// 9: Best compression
zos.setLevel(Deflater.BEST_COMPRESSION); 

Handling Character Encoding (Crucial for Non-ASCII Names)

ZIP files store filenames using a specific character encoding. If you don't specify it, Java uses the platform's default encoding, which can lead to issues with filenames containing special characters (e.g., , , ) on different operating systems.

The Best Practice: Always specify StandardCharsets.UTF_8.

import java.nio.charset.StandardCharsets;
// When creating the ZipEntry, provide the name as a byte array encoded in UTF-8
String fileName = "résumé.txt";
byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
ZipEntry zipEntry = new ZipEntry(fileNameBytes);
zos.putNextEntry(zipEntry);

Closing Streams

Always close your streams. Using try-with-resources is the most reliable way to do this.

  • zos.finish(): This method writes all pending data to the underlying output stream and finishes the ZIP file structure (e.g., the Central Directory). It's good practice to call this before the zos is closed, though close() often does this implicitly. Using try-with-resources handles this for you.

Common Pitfalls

  1. Not Closing ZipEntry: Forgetting zos.closeEntry() after writing a file will result in a corrupted ZIP file. The next file's data will be written as part of the previous entry.
  2. Incorrect Path Handling: When zipping directories, not using Path.relativize() can lead to absolute paths being stored in the ZIP, or the directory structure being flattened.
  3. Character Encoding Issues: As mentioned, not specifying UTF-8 can break file names for users on different systems.

Alternatives in Modern Java (Java 8+)

For simpler use cases, the java.nio.file package (introduced in Java 7) provides a more convenient Files utility class.

Java ZipOutputStream如何高效压缩多文件?-图3
(图片来源网络,侵删)
import java.io.IOException;
import java.nio.file.*;
import java
分享:
扫描分享到社交APP
上一篇
下一篇