Java学习之——“IO流“的进阶流之压缩流的学习
一、压缩流核心概念
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);// 将文件内容写入ZIPbyte[] 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();}}
}