Java读取和解压ZIP文件的完整示例代码

原创 2025-07-04 10:26:45编程技术
871

ZIP文件是常见的压缩格式,Java标准库提供了强大的java.util.zip包来处理ZIP文件。本文ZHANID工具网将通过一个完整的示例,展示如何使用Java读取和解压ZIP文件,包括处理中文文件名、大文件分块读取等高级特性。

一、基础解压实现

1. 最简单的解压方法

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

public class SimpleZipExtractor {
  public static void extractZip(String zipFilePath, String destDir) throws IOException {
    File destDirectory = new File(destDir);
    if (!destDirectory.exists()) {
      destDirectory.mkdirs();
    }

    try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath))) {
      ZipEntry entry = zipIn.getNextEntry();
      while (entry != null) {
        String filePath = destDir + File.separator + entry.getName();
        if (!entry.isDirectory()) {
          // 如果是文件,则解压文件
          extractFile(zipIn, filePath);
        } else {
          // 如果是目录,则创建目录
          File dir = new File(filePath);
          dir.mkdirs();
        }
        zipIn.closeEntry();
        entry = zipIn.getNextEntry();
      }
    }
  }

  private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
      byte[] bytesIn = new byte[4096];
      int read;
      while ((read = zipIn.read(bytesIn)) != -1) {
        bos.write(bytesIn, 0, read);
      }
    }
  }

  public static void main(String[] args) {
    String zipFilePath = "example.zip";
    String destDir = "output";
    try {
      extractZip(zipFilePath, destDir);
      System.out.println("解压完成!");
    } catch (IOException e) {
      System.err.println("解压失败: " + e.getMessage());
    }
  }
}

关键点说明:

  1. 使用ZipInputStream逐个读取ZIP条目

  2. 通过ZipEntry.isDirectory()判断是文件还是目录

  3. 使用缓冲区(4096字节)提高IO效率

  4. 自动关闭资源(try-with-resources)

二、高级功能实现

1. 处理中文文件名(解决乱码问题)

import java.io.*;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ChineseZipExtractor {
  public static void extractZipWithCharset(String zipFilePath, String destDir, String charsetName) throws IOException {
    File destDirectory = new File(destDir);
    if (!destDirectory.exists()) {
      destDirectory.mkdirs();
    }

    Charset charset = Charset.forName(charsetName);
    
    try (InputStream fis = new FileInputStream(zipFilePath);
       BufferedInputStream bis = new BufferedInputStream(fis);
       // 使用Apache Commons Compress库的ZipInputStream支持指定编码
       // 这里演示自定义处理方式(实际建议使用第三方库)
       ZipInputStream zipIn = new ZipInputStream(bis, charset)) {
      
      // 替代方案:如果无法修改ZipInputStream,可以这样处理文件名
      /*
      ZipInputStream zipIn = new ZipInputStream(bis);
      ZipEntry entry;
      while ((entry = zipIn.getNextEntry()) != null) {
        // 这里需要自定义解析逻辑,因为标准ZipInputStream不支持编码设置
        // 实际项目中建议使用Apache Commons Compress
        String entryName = entry.getName();
        // ...自定义解码逻辑...
      }
      */
      
      ZipEntry entry;
      while ((entry = zipIn.getNextEntry()) != null) {
        String filePath = destDir + File.separator + entry.getName();
        if (!entry.isDirectory()) {
          extractFile(zipIn, filePath);
        } else {
          new File(filePath).mkdirs();
        }
        zipIn.closeEntry();
      }
    }
  }

  // 更推荐的做法:使用Apache Commons Compress库
  public static void extractZipWithCommonsCompress(String zipFilePath, String destDir, String charsetName) throws IOException {
    File destDirectory = new File(destDir);
    if (!destDirectory.exists()) {
      destDirectory.mkdirs();
    }

    try (org.apache.commons.compress.archivers.zip.ZipFile zipFile = 
        new org.apache.commons.compress.archivers.zip.ZipFile(zipFilePath, charsetName)) {
      
      for (org.apache.commons.compress.archivers.zip.ZipArchiveEntry entry : zipFile.getEntries()) {
        String filePath = destDir + File.separator + entry.getName();
        if (!entry.isDirectory()) {
          try (InputStream is = zipFile.getInputStream(entry);
             OutputStream os = new FileOutputStream(filePath)) {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = is.read(buffer)) > 0) {
              os.write(buffer, 0, len);
            }
          }
        } else {
          new File(filePath).mkdirs();
        }
      }
    }
  }

  // 文件提取方法与之前相同
  private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
    // ...同上...
  }
}

解决方案说明

  1. 标准Java库的ZipInputStream不支持直接指定文件名编码

  2. 推荐使用Apache Commons Compress库(org.apache.commons.compress

  3. 添加Maven依赖:

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-compress</artifactId>
  <version>1.21</version>
</dependency>

java.webp

2. 处理大文件(分块读取)

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

public class LargeFileZipExtractor {
  // 使用固定大小的缓冲区处理大文件
  private static final int BUFFER_SIZE = 8192; // 8KB缓冲区

  public static void extractLargeZip(String zipFilePath, String destDir) throws IOException {
    File destDirectory = new File(destDir);
    if (!destDirectory.exists()) {
      destDirectory.mkdirs();
    }

    try (ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFilePath)))) {
      ZipEntry entry;
      while ((entry = zipIn.getNextEntry()) != null) {
        String filePath = destDir + File.separator + entry.getName();
        if (!entry.isDirectory()) {
          extractLargeFile(zipIn, filePath);
        } else {
          new File(filePath).mkdirs();
        }
        zipIn.closeEntry();
      }
    }
  }

  private static void extractLargeFile(ZipInputStream zipIn, String filePath) throws IOException {
    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
      byte[] buffer = new byte[BUFFER_SIZE];
      int bytesRead;
      while ((bytesRead = zipIn.read(buffer)) != -1) {
        bos.write(buffer, 0, bytesRead);
      }
    }
  }
}

优化点

  1. 使用8KB缓冲区平衡内存使用和IO效率

  2. 适合处理GB级别的大文件

  3. 避免一次性加载整个文件到内存

3. 完整示例(包含进度显示和异常处理)

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

public class CompleteZipExtractor {
  private static final int BUFFER_SIZE = 4096;

  public static void extractZipWithProgress(String zipFilePath, String destDir) {
    File destDirectory = new File(destDir);
    if (!destDirectory.exists()) {
      destDirectory.mkdirs();
    }

    long totalEntries = countZipEntries(zipFilePath);
    long processedEntries = 0;

    try (ZipInputStream zipIn = new ZipInputStream(
        new BufferedInputStream(new FileInputStream(zipFilePath)))) {

      ZipEntry entry;
      while ((entry = zipIn.getNextEntry()) != null) {
        processedEntries++;
        String filePath = destDir + File.separator + entry.getName();
        
        System.out.printf("解压进度: %d/%d (%.1f%%) - %s%n",
            processedEntries, totalEntries,
            (processedEntries * 100.0 / totalEntries),
            entry.getName());

        if (entry.isDirectory()) {
          new File(filePath).mkdirs();
        } else {
          extractFileWithValidation(zipIn, filePath, entry.getSize());
        }
        zipIn.closeEntry();
      }
      System.out.println("解压完成!共处理 " + processedEntries + " 个文件");
    } catch (IOException e) {
      System.err.println("解压过程中发生错误: " + e.getMessage());
      e.printStackTrace();
    }
  }

  private static long countZipEntries(String zipFilePath) throws IOException {
    long count = 0;
    try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath))) {
      while (zipIn.getNextEntry() != null) {
        count++;
      }
      // 重置流以便后续读取
      // 注意:实际实现中需要更复杂的处理,这里简化演示
      // 正确做法是重新打开流或使用其他方式计数
    }
    // 更可靠的实现方式(需要两次读取或使用ZipFile)
    // 这里简化处理,实际项目建议使用ZipFile计数
    return count;
  }

  // 更可靠的计数方法(使用ZipFile)
  public static long countZipEntriesReliable(String zipFilePath) throws IOException {
    try (java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(zipFilePath)) {
      return zipFile.size();
    }
  }

  private static void extractFileWithValidation(ZipInputStream zipIn, String filePath, long expectedSize) 
      throws IOException {
    File outputFile = new File(filePath);
    File parentDir = outputFile.getParentFile();
    if (parentDir != null && !parentDir.exists()) {
      parentDir.mkdirs();
    }

    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
      byte[] buffer = new byte[BUFFER_SIZE];
      long totalRead = 0;
      int bytesRead;
      
      while ((bytesRead = zipIn.read(buffer)) != -1) {
        bos.write(buffer, 0, bytesRead);
        totalRead += bytesRead;
      }
      
      // 验证文件大小
      if (expectedSize > 0 && totalRead != expectedSize) {
        System.err.println("警告: 文件大小不匹配 - " + filePath);
        System.err.printf("预期: %d 字节, 实际: %d 字节%n", expectedSize, totalRead);
      }
    }
  }

  public static void main(String[] args) {
    String zipFilePath = "large_example.zip";
    String destDir = "output";
    extractZipWithProgress(zipFilePath, destDir);
  }
}

完整功能

  1. 显示解压进度(文件数/百分比)

  2. 验证解压后的文件大小

  3. 自动创建父目录

  4. 完善的异常处理

  5. 资源自动关闭

三、最佳实践总结

1. 资源管理

  • 始终使用try-with-resources确保流关闭

  • 对于大文件,使用适当大小的缓冲区(4KB-8KB)

2. 错误处理

  • 区分可恢复错误和致命错误

  • 提供有意义的错误信息

  • 考虑记录错误日志

3. 性能优化

  • 使用缓冲流(BufferedInputStream/BufferedOutputStream)

  • 对于大量小文件,考虑批量操作

  • 避免在循环中创建不必要的对象

4. 安全性考虑

  • 验证解压路径,防止目录遍历攻击

  • 检查文件权限

  • 限制解压文件大小防止内存耗尽

5. 编码处理

  • 对于中文文件名,优先使用Apache Commons Compress

  • 如果必须使用标准库,考虑以下替代方案:

    • 使用ZipFile类配合自定义编码处理

    • 在读取文件名后进行编码转换(复杂且不可靠)

四、替代方案比较

方案 优点 缺点
标准库(java.util.zip) 无需额外依赖,JDK自带 中文支持差,功能有限
Apache Commons Compress 支持多种压缩格式,编码处理好 增加依赖
Zip4j 功能丰富,支持加密 商业使用需注意许可
7-Zip-JBinding 高性能,支持7z格式 平台相关,配置复杂

推荐选择

  • 简单需求:标准库

  • 需要中文支持:Apache Commons Compress

  • 企业级应用:Zip4j(检查许可)

五、完整项目结构示例

src/
├── main/
│  ├── java/
│  │  └── com/
│  │    └── example/
│  │      ├── zip/
│  │      │  ├── SimpleZipExtractor.java
│  │      │  ├── ChineseZipExtractor.java
│  │      │  ├── LargeFileZipExtractor.java
│  │      │  └── CompleteZipExtractor.java
│  │      └── Main.java
│  └── resources/
└── test/
  └── java/
    └── com/
      └── example/
        └── zip/
          └── ZipExtractorTest.java

通过本文的完整示例和详细说明,您应该已经掌握了Java处理ZIP文件的各种方法,从基础解压到高级特性实现。根据实际项目需求选择合适的方案,并注意处理边界情况和异常,可以构建出健壮可靠的ZIP文件处理功能。

java zip
THE END
战地网
频繁记录吧,生活的本意是开心

相关推荐

JavaDB怎么进不去了?3个常见原因和解决办法
问题出在哪?先查连接状态 JavaDB进不去,最常见的就是服务没跑起来。你看,JavaDB默认用1527端口监听。如果连不上,大概率是它没启动或者端口被占了。用这段代码快速检测:...
2026-04-02 新闻资讯
260

Java版连锁挖矿mod叫什么名字
主流连锁挖矿mod名字汇总 根据抖音和百度贴吧的玩家反馈,Java版《我的世界》连锁挖矿mod有好几个常用名字。最常被推荐的是FTB Ultimine。它在抖音多个视频里被提到,能一...
2026-04-02 新闻资讯
416

Java日志管理框架:Log4j、SLF4J、Logback对比与使用方法详解
java主流日志框架中,Log4j 1.x作为早期标准,Log4j 2.x通过重构实现性能飞跃,Logback作为Log4j的继承者以原生SLF4J支持成为主流选择,而SLF4J作为日志门面,通过抽象层实现...
2025-09-15 编程技术
1032

Java 与 MySQL 性能优化:MySQL全文检索查询优化实践
本文聚焦Java与MySQL协同环境下的全文检索优化实践,从索引策略、查询调优、参数配置到Java层优化,深入解析如何释放全文检索的潜力,为高并发、大数据量场景提供稳定高效的搜...
2025-09-13 编程技术
1001

JavaScript 中 instanceof 的作用及使用方法详解
在 JavaScript 的类型检查体系中,instanceof 是一个重要的操作符,用于判断一个对象是否属于某个构造函数的实例或其原型链上的类型。本文ZHANID工具网将系统讲解 instanceof...
2025-09-11 编程技术
1089

Java与MySQL数据库连接实战:JDBC使用教程
JDBC(Java Database Connectivity)作为Java标准API,为开发者提供了统一的数据访问接口,使得Java程序能够无缝连接各类关系型数据库。本文ZHANID工具网将以MySQL数据库为例...
2025-09-11 编程技术
892