Java-IO流之压缩与解压缩流详解

Java-IO流之压缩与解压缩流详解

    • 一、压缩与解压缩概述
      • 1.1 基本概念
      • 1.2 Java中的压缩类库
      • 1.3 核心类与接口
    • 二、ZIP压缩与解压缩
      • 2.1 ZIP格式简介
      • 2.2 使用ZipOutputStream创建ZIP文件
      • 2.3 使用ZipInputStream读取ZIP文件
    • 三、GZIP压缩与解压缩
      • 3.1 GZIP格式简介
      • 3.2 使用GZIPOutputStream压缩文件
      • 3.3 使用GZIPInputStream解压文件
    • 四、压缩流的高级应用
      • 4.1 计算压缩文件的校验和
      • 4.2 创建分卷ZIP文件
      • 4.3 压缩多个文件并保持目录结构
    • 五、压缩流的最佳实践
      • 5.1 使用缓冲区提高性能
      • 5.2 处理大文件时的内存优化
      • 5.3 处理中文文件名
      • 5.4 使用try-with-resources语句
    • 六、常见问题与解决方案
      • 6.1 中文文件名乱码
      • 6.2 压缩率不理想
      • 6.3 性能问题
      • 6.4 压缩文件损坏

Java中处理文件和数据时压缩和解压缩是常见的需求,Java IO体系提供了强大的压缩和解压缩流,通过ZipOutputStream、ZipInputStream、GZIPOutputStream和GZIPInputStream等类,我们可以轻松实现文件压缩、归档和解压缩等功能。本文我将深入探讨Java压缩与解压缩流的原理、使用方法及高级应用,帮你全面掌握这一重要技术。

一、压缩与解压缩概述

1.1 基本概念

  • 压缩(Compression):将数据转换为占用更少存储空间的格式的过程
  • 解压缩(Decompression):将压缩数据恢复为原始格式的过程
  • 归档(Archiving):将多个文件或目录组合成一个文件的过程

1.2 Java中的压缩类库

Java提供了多种压缩格式的支持,主要包括:

  • ZIP:常用的归档和压缩格式,支持多个文件和目录
  • GZIP:主要用于单个文件的压缩,不支持多文件归档
  • JAR:基于ZIP格式的Java归档文件,用于打包Java类和资源
  • BZIP2:提供更高压缩比的压缩格式
  • DEFLATE:ZIP和GZIP使用的底层压缩算法

1.3 核心类与接口

  • ZipOutputStream:用于创建ZIP归档文件
  • ZipInputStream:用于读取ZIP归档文件
  • GZIPOutputStream:用于创建GZIP压缩文件
  • GZIPInputStream:用于读取GZIP压缩文件
  • ZipEntry:表示ZIP归档中的一个条目(文件或目录)
  • CheckedOutputStream:用于计算校验和的输出流
  • Adler32/Crc32:常用的校验和算法实现

二、ZIP压缩与解压缩

2.1 ZIP格式简介

ZIP是一种常见的归档和压缩格式,支持:

  • 存储多个文件和目录
  • 每个文件可独立压缩
  • 支持文件路径和文件属性
  • 包含文件目录信息

2.2 使用ZipOutputStream创建ZIP文件

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

public class ZipExample {
    public static void main(String[] args) {
        String[] filesToZip = {"file1.txt", "file2.txt", "directory/"};
        String zipFileName = "archive.zip";
        
        try (ZipOutputStream zipOut = new ZipOutputStream(
                new FileOutputStream(zipFileName))) {
            
            for (String filePath : filesToZip) {
                File file = new File(filePath);
                if (file.exists()) {
                    addToZip(file, file.getName(), zipOut);
                }
            }
            
            System.out.println("ZIP文件创建成功: " + zipFileName);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void addToZip(File file, String entryName, ZipOutputStream zipOut) throws IOException {
        if (file.isDirectory()) {
            // 处理目录
            zipOut.putNextEntry(new ZipEntry(entryName + "/"));
            zipOut.closeEntry();
            
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    addToZip(child, entryName + "/" + child.getName(), zipOut);
                }
            }
        } else {
            // 处理文件
            try (FileInputStream fis = new FileInputStream(file)) {
                ZipEntry zipEntry = new ZipEntry(entryName);
                zipOut.putNextEntry(zipEntry);
                
                byte[] bytes = new byte[1024];
                int length;
                while ((length = fis.read(bytes)) >= 0) {
                    zipOut.write(bytes, 0, length);
                }
                
                zipOut.closeEntry();
            }
        }
    }
}

2.3 使用ZipInputStream读取ZIP文件

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

public class UnzipExample {
    public static void main(String[] args) {
        String zipFileName = "archive.zip";
        String destDirectory = "extracted";
        
        try (ZipInputStream zipIn = new ZipInputStream(
                new FileInputStream(zipFileName))) {
            
            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.mkdirs();
                }
                zipIn.closeEntry();
                entry = zipIn.getNextEntry();
            }
            
            System.out.println("ZIP文件解压成功到: " + destDirectory);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(filePath))) {
            
            byte[] bytesIn = new byte[1024];
            int read;
            while ((read = zipIn.read(bytesIn)) != -1) {
                bos.write(bytesIn, 0, read);
            }
        }
    }
}

三、GZIP压缩与解压缩

3.1 GZIP格式简介

GZIP是一种常用的文件压缩格式,特点是:

  • 主要用于单个文件的压缩
  • 不支持多文件归档
  • 基于DEFLATE算法
  • 通常用于压缩文本文件、日志文件等

3.2 使用GZIPOutputStream压缩文件

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

public class GzipExample {
    public static void main(String[] args) {
        String sourceFile = "large_file.txt";
        String compressedFile = "large_file.txt.gz";
        
        try (FileInputStream fis = new FileInputStream(sourceFile);
             GZIPOutputStream gzos = new GZIPOutputStream(
                     new FileOutputStream(compressedFile))) {
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                gzos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件压缩成功: " + compressedFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.3 使用GZIPInputStream解压文件

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

public class GunzipExample {
    public static void main(String[] args) {
        String compressedFile = "large_file.txt.gz";
        String decompressedFile = "large_file_uncompressed.txt";
        
        try (GZIPInputStream gzis = new GZIPInputStream(
                new FileInputStream(compressedFile));
             FileOutputStream fos = new FileOutputStream(decompressedFile)) {
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = gzis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件解压成功: " + decompressedFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、压缩流的高级应用

4.1 计算压缩文件的校验和

import java.io.*;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ChecksumExample {
    public static void main(String[] args) {
        String sourceFile = "data.txt";
        String zipFile = "data_with_checksum.zip";
        
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             CheckedOutputStream cos = new CheckedOutputStream(fos, new Adler32());
             ZipOutputStream zos = new ZipOutputStream(cos);
             FileInputStream fis = new FileInputStream(sourceFile)) {
            
            // 添加文件到ZIP
            ZipEntry entry = new ZipEntry("data.txt");
            zos.putNextEntry(entry);
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
            }
            
            zos.closeEntry();
            
            // 获取校验和
            long checksum = cos.getChecksum().getValue();
            System.out.println("文件校验和: " + checksum);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.2 创建分卷ZIP文件

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

public class SplitZipExample {
    private static final int SPLIT_SIZE = 1024 * 1024; // 1MB
    
    public static void main(String[] args) {
        String sourceFile = "large_file.dat";
        String baseZipName = "split_archive.zip";
        
        try (FileInputStream fis = new FileInputStream(sourceFile)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            int partNumber = 1;
            long currentSize = 0;
            ZipOutputStream zos = null;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                // 检查是否需要创建新的分卷
                if (zos == null || currentSize + bytesRead > SPLIT_SIZE) {
                    if (zos != null) {
                        zos.close();
                    }
                    
                    String zipFileName = baseZipName + "." + String.format("%02d", partNumber++);
                    zos = new ZipOutputStream(new FileOutputStream(zipFileName));
                    zos.putNextEntry(new ZipEntry("large_file.dat"));
                    currentSize = 0;
                    System.out.println("创建分卷: " + zipFileName);
                }
                
                zos.write(buffer, 0, bytesRead);
                currentSize += bytesRead;
            }
            
            if (zos != null) {
                zos.closeEntry();
                zos.close();
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.3 压缩多个文件并保持目录结构

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

public class ZipDirectoryExample {
    public static void main(String[] args) {
        String sourceDir = "my_directory";
        String zipFile = "directory_archive.zip";
        
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
            File directory = new File(sourceDir);
            if (directory.exists() && directory.isDirectory()) {
                zipDirectory(directory, directory.getName(), zos);
                System.out.println("目录压缩成功: " + zipFile);
            } else {
                System.out.println("源目录不存在或不是目录");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void zipDirectory(File directory, String parentPath, ZipOutputStream zos) throws IOException {
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    zipDirectory(file, parentPath + "/" + file.getName(), zos);
                } else {
                    String entryName = parentPath + "/" + file.getName();
                    ZipEntry zipEntry = new ZipEntry(entryName);
                    zos.putNextEntry(zipEntry);
                    
                    try (FileInputStream fis = new FileInputStream(file)) {
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            zos.write(buffer, 0, bytesRead);
                        }
                    }
                    
                    zos.closeEntry();
                }
            }
        }
    }
}

五、压缩流的最佳实践

5.1 使用缓冲区提高性能

在读写压缩流时,始终使用缓冲区可以显著提高性能:

// 使用BufferedInputStream和BufferedOutputStream提高性能
try (BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("source.txt"));
     GZIPOutputStream gzos = new GZIPOutputStream(
             new BufferedOutputStream(
                     new FileOutputStream("source.txt.gz")))) {
    
    byte[] buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = bis.read(buffer)) != -1) {
        gzos.write(buffer, 0, bytesRead);
    }
}

5.2 处理大文件时的内存优化

对于非常大的文件,避免一次性将整个文件加载到内存中:

// 分块处理大文件
try (ZipOutputStream zos = new ZipOutputStream(
        new FileOutputStream("large_archive.zip"))) {
    
    ZipEntry entry = new ZipEntry("large_file.dat");
    zos.putNextEntry(entry);
    
    try (FileInputStream fis = new FileInputStream("large_file.dat")) {
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = fis.read(buffer)) != -1) {
            zos.write(buffer, 0, bytesRead);
        }
    }
    
    zos.closeEntry();
}

5.3 处理中文文件名

在处理包含中文文件名的ZIP文件时,需要指定正确的字符编码:

// 指定GBK编码处理中文文件名
ZipOutputStream zos = new ZipOutputStream(
        new FileOutputStream("chinese_files.zip"), 
        java.nio.charset.Charset.forName("GBK"));

5.4 使用try-with-resources语句

确保所有流资源被正确关闭,避免资源泄漏:

try (ZipInputStream zis = new ZipInputStream(
        new FileInputStream("archive.zip"))) {
    
    // 处理ZIP文件
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        // 处理每个条目
    }
} catch (IOException e) {
    e.printStackTrace();
}

六、常见问题与解决方案

6.1 中文文件名乱码

当ZIP文件中的文件名包含中文时,可能会出现乱码。解决方案是在创建ZipOutputStreamZipInputStream时指定正确的字符编码:

// 读取包含中文文件名的ZIP文件
try (ZipInputStream zis = new ZipInputStream(
        new FileInputStream("chinese_archive.zip"),
        java.nio.charset.Charset.forName("GBK"))) {
    
    // 处理ZIP文件
}

6.2 压缩率不理想

如果压缩率不理想,可以考虑:

  • 检查文件类型:某些文件(如图片、视频)本身已经是压缩格式,再次压缩效果不佳
  • 调整压缩级别:对于支持压缩级别的流,可以尝试不同的压缩级别
  • 对于文本文件,通常可以获得较高的压缩率

6.3 性能问题

在处理大量数据时,压缩操作可能会成为性能瓶颈。可以考虑:

  • 使用多线程并行压缩
  • 调整缓冲区大小
  • 对于大文件,考虑使用更高效的压缩算法或工具

6.4 压缩文件损坏

如果压缩文件损坏,可能的原因包括:

  • 写入过程中出现异常,导致文件不完整
  • 校验和不匹配
  • 文件传输过程中损坏

解决方案包括:

  • 使用校验和验证文件完整性
  • 确保在关闭流之前完成所有写入操作
  • 使用可靠的传输方式

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

你可能感兴趣的:(JavaSE,java)