Java学习之——“IO流“的进阶流之压缩流的学习

发布于:2025-09-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、压缩流核心概念

        Java 的压缩流并不是一种独立的流,而是一组基于装饰器模式的过滤流(处理流)。它们“装饰”在基本的字节流之上,在写入数据时自动进行压缩,在读取数据时自动进行解压缩。

        核心思想:在IO传输过程中,动态地对字节数据进行压缩或解压,从而节省存储空间或网络带宽。

        ZIP 压缩流是 Java 中处理 ZIP 格式文件的核心 API,它允许我们以编程方式创建、读取和修改 ZIP 压缩文件。

Java 处理 ZIP 文件主要依赖于 java.util.zip 包中的以下几个核心类:

类名 作用描述
ZipOutputStream 用于创建 ZIP 压缩文件并向其中添加条目
ZipInputStream 用于读取 ZIP 压缩文件并提取其中的条目
ZipEntry 表示 ZIP 文件中的一个压缩条目(文件或目录)
ZipFile 提供了另一种读取 ZIP 文件的方式,支持随机访问

基本压缩流程

创建 ZIP 文件的基本流程是:

  • 创建 FileOutputStream 指向目标 ZIP 文件
  • 使用 ZipOutputStream 包装该输出流
  • 为每个要压缩的文件创建 ZipEntry 并添加到 ZIP 流
  • 将文件内容写入到对应的 ZIP 条目
  • 关闭所有资源

基本解压流程

解压 ZIP 文件的基本流程是:

  • 创建 FileInputStream 指向 ZIP 文件
  • 使用 ZipInputStream 包装该输入流
  • 循环获取 ZIP 文件中的每个条目
  • 为每个条目创建对应的文件/目录
  • 将条目内容写入到目标文件
  • 关闭所有资源

二、ZipEntry作用的详解

        ZipEntry 是 Java 的 java.util.zip 包中的一个类。它的核心作用代表一个ZIP压缩文件中的单个条目(entry)。 你可以把它理解为一个ZIP文件内部的“元数据”或“目录项”,它描述了压缩包中每一个文件或目录的信息,但它本身并不包含文件的实际数据内容。

简单来说:

  • 一个 ZipFile 或 ZipInputStream 对象代表整个ZIP文件。
  • 一个 ZipEntry 对象代表ZIP文件里面的一个具体文件(如 document.txt)或目录(如 images/)。

        ZipInputStream 是用于按顺序读取ZIP文件数据流的通道,而 ZipEntry 则是这个流中每个独立文件单元的元数据标识和分隔符。

核心作用详解

ZipEntry 的主要作用可以分为以下几个方面:

1. 元数据描述

        这是 ZipEntry 最基本和最重要的作用。它存储了ZIP条目(即压缩包里的一个文件)的详细信息,包括:

  • 名称 (getName()): 条目在ZIP文件中的路径和文件名,例如 "data/config.json" 或 "photos/2024/summer.jpg"
  • 压缩后大小 (getCompressedSize()): 文件被压缩后在ZIP包中所占的字节数。
  • 原始大小 (getSize()): 文件未压缩时的原始字节数。如果未知,则返回 -1
  • CRC-32 校验和 (getCrc()): 用于验证解压后数据的完整性。将解压出的数据计算出的CRC值与这里存储的值对比,可以判断数据是否损坏。
  • 修改时间 (getTime()): 文件最后一次修改的时间(自公元纪年开始的毫秒数)。
  • 压缩方法 (getMethod()): 文件使用的压缩算法。通常是 DEFLATED(压缩)或 STORED(不压缩,仅存储)。
  • 注释 (getComment()): 条目的可选注释信息。
2. 数据提取的桥梁

        当您想从ZIP文件中读取(解压)某个特定文件时,ZipEntry 是关键桥梁。流程通常是:

  • 通过 ZipInputStream 或 ZipFile 遍历ZIP文件,获取下一个 ZipEntry
  • 检查这个 ZipEntry 的名称,确定它是不是你想要的文件。
  • 如果是要找的文件,然后 再从 ZipInputStream 中读取该条目对应的实际压缩数据,并进行解压。
// 示例:使用 ZipInputStream 读取ZIP文件中的第一个条目
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    // 获取ZIP文件中的第一个条目(entry)
    ZipEntry entry = zis.getNextEntry();
    
    if (entry != null) {
        System.out.println("正在解压: " + entry.getName());
        System.out.println("原始大小: " + entry.getSize());
        
        // 现在,从 zis 中读取的数据就是这个 entry 对应的文件内容
        byte[] buffer = new byte[1024];
        int len;
        while ((len = zis.read(buffer)) > 0) {
            // 将 buffer 中的数据写入到目标文件
            // ...
        }
        // 关闭当前条目,准备读取下一个
        zis.closeEntry();
    }
}

三、压缩流代码示例

1.压缩

压缩单个文件

import java.io.*;
import java.util.zip.*;

public class ZipSingleFileExample {
    public static void main(String[] args) {
        String sourceFile = "document.txt";
        String zipFile = "compressed.zip";
        
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos);
             FileInputStream fis = new FileInputStream(sourceFile)) {
            
            // 创建ZIP条目
            ZipEntry zipEntry = new ZipEntry(sourceFile);
            zos.putNextEntry(zipEntry);
            
            // 设置压缩级别 (0-9)
            zos.setLevel(Deflater.BEST_COMPRESSION);
            
            // 将文件内容写入ZIP
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) >= 0) {
                zos.write(buffer, 0, length);
            }
            
            // 关闭当前条目
            zos.closeEntry();
            
            System.out.println("文件压缩完成: " + zipFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

压缩多个文件

import java.io.*;
import java.util.zip.*;

public class ZipMultipleFilesExample {
    public static void main(String[] args) {
        String[] filesToZip = {"file1.txt", "file2.txt", "image.jpg"};
        String zipFile = "archive.zip";
        
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            
            // 设置压缩级别
            zos.setLevel(Deflater.DEFAULT_COMPRESSION);
            
            for (String file : filesToZip) {
                try (FileInputStream fis = new FileInputStream(file)) {
                    // 为每个文件创建ZIP条目
                    ZipEntry zipEntry = new ZipEntry(file);
                    zos.putNextEntry(zipEntry);
                    
                    // 写入文件内容
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) >= 0) {
                        zos.write(buffer, 0, length);
                    }
                    
                    // 关闭当前条目
                    zos.closeEntry();
                } catch (IOException e) {
                    System.err.println("处理文件时出错: " + file);
                    e.printStackTrace();
                }
            }
            
            System.out.println("多文件压缩完成: " + zipFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

压缩目录(包含子目录)

import java.io.*;
import java.util.zip.*;

public class ZipDirectoryExample {
    public static void main(String[] args) {
        String sourceDir = "docs";
        String zipFile = "docs.zip";
        
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            
            zipDirectory(sourceDir, sourceDir, zos);
            System.out.println("目录压缩完成: " + zipFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void zipDirectory(String rootDir, String sourceDir, ZipOutputStream zos) throws IOException {
        File dir = new File(sourceDir);
        File[] files = dir.listFiles();
        
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                // 递归处理子目录
                zipDirectory(rootDir, file.getAbsolutePath(), zos);
                continue;
            }
            
            try (FileInputStream fis = new FileInputStream(file)) {
                // 创建相对路径的ZIP条目
                String zipPath = file.getAbsolutePath().substring(
                    new File(rootDir).getAbsolutePath().length() + 1);
                
                ZipEntry zipEntry = new ZipEntry(zipPath);
                zos.putNextEntry(zipEntry);
                
                // 写入文件内容
                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) >= 0) {
                    zos.write(buffer, 0, length);
                }
                
                zos.closeEntry();
            }
        }
    }
}

2.解压缩

import java.io.*;
import java.util.zip.*;

public class UnzipExample {
    public static void main(String[] args) {
        String zipFile = "archive.zip";
        String destDirectory = "extracted";
        
        // 创建目标目录
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
        
        try (FileInputStream fis = new FileInputStream(zipFile);
             ZipInputStream zis = new ZipInputStream(fis)) {
            
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String filePath = destDirectory + File.separator + entry.getName();
                
                // 防止ZIP Slip攻击
                File destFile = new File(filePath);
                String canonicalDestPath = destFile.getCanonicalPath();
                String canonicalDestDir = destDir.getCanonicalPath();
                
                if (!canonicalDestPath.startsWith(canonicalDestDir + File.separator)) {
                    throw new IOException("恶意ZIP文件: 尝试解压到非法路径 " + entry.getName());
                }
                
                if (entry.isDirectory()) {
                    // 创建目录
                    if (!destFile.exists()) {
                        destFile.mkdirs();
                    }
                } else {
                    // 确保父目录存在
                    File parent = destFile.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }
                    
                    // 解压文件
                    try (FileOutputStream fos = new FileOutputStream(destFile);
                         BufferedOutputStream bos = new BufferedOutputStream(fos)) {
                        
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = zis.read(buffer)) >= 0) {
                            bos.write(buffer, 0, length);
                        }
                    }
                }
                
                zis.closeEntry();
            }
            
            System.out.println("ZIP文件解压完成到: " + destDirectory);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}