【一文通关】Java操作zip文件

572 阅读5分钟

什么是压缩文件

Zip文件是一种常见的归档文件格式,通常用于将多个文件打包成一个文件进行传输或存储。Zip文件中可以包含多个文件和文件夹,这些文件和文件夹会被压缩成一个单独的文件,以减少存储空间和传输带宽的消耗。 人话:使文件更小,更集中,更利于数据传递

Zip文件格式是一种开放的标准,它由PKWARE公司于1989年发布,目前已经被广泛支持和使用。Zip文件格式不仅可以在Windows和Mac OS X等操作系统上使用,还可以在Linux和其他UNIX操作系统上使用,因此它是一种跨平台的归档格式。人话:跨平台,各个操作系统均能接收并处理

Zip文件通常可以通过多种方式进行创建和解压缩,例如使用Windows操作系统附带的压缩工具、使用第三方的压缩软件(如WinRAR、7-Zip等)、使用命令行工具(如Linux系统上的zip和unzip命令)等。在Java程序中,可以使用Java自带的ZipFile和ZipOutputStream类来读取和创建Zip文件。 人话:可以使用操作系统自带的压缩软件,也可以使用Java的具体zip类来操作zip

本文主要是讲解Java里面对zip文件的操作:

ZipInputStream与InputStream的关系

InputStream是所有输入流的父类,包括字节流与字符流,它是按字节进行读取数据的,然后我们的压缩文件是以文件为单位进行解压缩的。 因此可以使用FileInputStream来将文件读入,再使用ZipInputStream(InputStream的装饰器类,不是直接继承InputStream)来对其进行解压缩

具体讲讲ZipInputStream与InputStream的关系吧:

首先,ZipInputStream 继承了 InflaterInputStream。InflaterInputStream类中定义了一些方法,如fill()、readBits()等。ZipInputStream类重写了这些方法,并添加了一些Zip文件特有的逻辑,例如从Zip文件中读取文件头、解压缩文件数据等。ZipInputStream的重写方法read也调用了父类的read方法:super.read(), 满足装饰器模式,调用自身方法添加特殊处理的同时,父类的方法正常调用

InflaterInputStream 继承了 FilterInputStream,并添加了对压缩数据的解压缩功能,同时满足装饰器模式。

FilterInputStream 继承了 InputStream ,将InputStream作为属性,为其装饰(添加功能)。

这样一层层的为InputStream的实现类添加功能,ZipInputStream就是其中之一的装饰器。

开始使用ZipInputStream

// 新建文件输入实体类new FileInputStream();
// 使用ZipInputStream装饰,添加独特的处理压缩文件的方法
ZipInputStream zip = new ZipInputStream(new FileInputStream("C:/Users/Lenovo/Desktop/笔记.zip"),   
Charset.forName("GBK"))

编码格式很重要,压缩文件的编码格式默认为UTF-8,但是也不全是,为此,大家可能常常出错误:

image.png

上面就是编码格式出错的情况。最简单的做法:出错了你就修改编码格式,GBK,UTF-8,GB2312等编码格式来回换。

也可以利用第三方工具或库可以对Zip文件名进行解码,比如[Apache Commons Compress](程序员的福音 - Apache Commons Compress - 知乎 (zhihu.com))

完整的解压文件并且读取文件内容的代码如下

完整解压代码,可以拿去直接用

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

public class Main {
    // 获得file下的文件内容
    public static void getFileContent(File file){
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for (File file1 : files) {
                getFileContent(file1);
            }
        }
        else {
            byte[] buffer = new byte[1024];
            try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file), buffer.length)) {
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer, 0, buffer.length)) != -1) {
                    for (int i = 0; i < bytesRead; i++) {
                        // 我以字节流输出的,格式太多了统一为字节流,可以在这里判断文件类型
                        System.out.print(buffer[i] + " ");
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws IOException {
        String zipFilePath = "C:\Users\Lenovo\Desktop\biji.zip"; // Zip文件路径
        String unzipFolderPath = "C:\Users\Lenovo\Desktop\biji"; // 解压后的文件夹路径

        // 创建解压后的文件夹
        File unzipFolder = new File(unzipFolderPath);
        if (!unzipFolder.exists()) {
            unzipFolder.mkdir();
        }

        // 创建Zip文件输入流
        FileInputStream fis = new FileInputStream(zipFilePath);
        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis), Charset.forName("GBK"));

        // 遍历Zip文件中的每个文件,并解压到指定文件夹中
        ZipEntry entry;
        byte[] buffer = new byte[1024];
        while ((entry = zis.getNextEntry()) != null) {
            String fileName = entry.getName();
            File file = new File(unzipFolderPath + File.separator + fileName);
            if (entry.isDirectory()) {
                file.mkdirs();
            } else {
                FileOutputStream fos = new FileOutputStream(file);
                BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
                int count;
                // 读取的内容放入缓冲区字节数组
                while ((count = zis.read(buffer, 0, buffer.length)) != -1) {
                    // 将缓冲区的内容写入文件
                    bos.write(buffer, 0, count);
                }
                // 这时剩余的部分在缓冲区,需要将没占满缓冲区的部分也一并写入文件,调用flush()
                bos.flush();
                bos.close();
            }
        }
        // 写成try(Resource)就可以不用手动关闭
        // 关闭输入流
        zis.close();
        fis.close();

        // 读取解压后的文件
        getFileContent(unzipFolder);
    }
}

小伙伴们可能会疑惑flush()的用法

BufferedOutputStream wirite源码:

@Override
public synchronized void write(byte b[], int off, int len) throws IOException {
    if (len >= buf.length) {
        /* If the request length exceeds the size of the output buffer,
           flush the output buffer and then write the data directly.
           In this way buffered streams will cascade harmlessly. */
        flushBuffer();
        out.write(b, off, len);
        return;
    }
    if (len > buf.length - count) {
        flushBuffer();
    }
    System.arraycopy(b, off, buf, count, len);
    count += len;
}

方法首先判断要写入的字节数是否大于缓冲区的大小,如果是,则先将缓冲区中的数据写入到输出流中,以避免数据丢失或者混乱。如果要写入的字节数小于等于缓冲区的大小,方法会检查缓冲区中剩余的空间是否足够存储要写入的数据,如果不够,则先将缓冲区中的数据写入到输出流中,以释放空间。如果剩余的空间足够存储要写入的数据,则将数据写入到缓冲区中,并更新缓冲区的计数器。注意这最后一句话,写入到缓冲区,并没有写入到输出流中,因此手动调用flush()方法


小贴士: _MACOSX文件夹是在Mac OS X操作系统上创建的隐藏文件夹。它通常出现在你通过Mac电脑将文件或文件夹压缩成ZIP文件时。这个文件夹包含与压缩文件相关的元数据,例如存储在文件中的资源信息和属性。 在大多数情况下,_MACOSX文件夹对于文件的使用没有任何影响。但是,如果您将ZIP文件发送给使用其他操作系统(如Windows)的人员,可能会导致问题,因为Windows系统无法处理这些元数据文件。在这种情况下,_MACOSX文件夹可能会显示在压缩文件中,并且可能会导致问题或不必要的混乱。 因此,如果您要与其他操作系统的用户共享ZIP文件,最好将_MACOSX文件夹从ZIP文件中删除。