Java、Zip流使用

目录

前言

一、ZipUtil

二、测试

Main

总结


前言

本章主要是对 ZipInputStream  ZipOutputStream 的使用,提供了对文件/文件夹压缩/解压方法。


一、ZipUtil

Zip工具类,提供压缩/解压方法。主要有以下特色:

  • 输入输出流使用缓冲流装饰。
  • 使用NIO的Files类操作文件和目录。
  • 使用Java8函数式接口(Predicate、Consumer)。
  • 少用或不用else,使用return;或continue;终止当前代码。
  • 更倾向方法而不是注释来解释代码。

提示:在使用zip()方法压缩文件时,似乎只能用unzip()方法解压文件(使用360Zip解压失败)。

package com.rocketmq.test;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/** 压缩/解压工具类
 * @Create: 2024/8/4
 * @Description:
 * @FileName: ZipUtil
 */
public class ZipUtil {
    /**
     * 缓冲区大小
     */
    private static final int BUFFER_SIZE = 256;

    /**
     * 文件末尾
     */
    private static final int EOF = -1;

    /**
     * 空字符串
     */
    private static final String EMPTY = "";

    /**
     * 断言字符串为空
     */
    private static final Predicate<String> isEmpty = str -> Objects.equals(str, EMPTY);

    /**
     * 压缩文件后缀
     */
    private static final String SUFFIX = ".zip";

    private ZipUtil() {
    }

    /**
     * 压缩文件/目录
     *
     * @param src 源目录/源文件
     * @param tar 目标目录
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    public static void zip(String src, String tar) throws IOException {
        tar = Files.isDirectory(Paths.get(src)) ?
                getZipNameOfDir(src, tar) : getZipNameOfFile(src, tar);
        try (ZipOutputStream zipOut = new ZipOutputStream(
                new BufferedOutputStream(Files.newOutputStream(Paths.get(tar))))) {
            zipOut.setComment("Zip done by Java");
            Path start = Paths.get(src);
            Files.walk(start)                                   // 遍历文件/文件夹
                    .skip(Files.isDirectory(start) ? 1L : 0L)   // 如果是文件夹,跳过根目录,因为文件已包含
                    .forEach(path -> writeToZip(zipOut, path)); // 将文件写入Zip流
        }
    }

    /**
     * 获取文件压缩包名
     *
     * @param src 源文件
     * @param tar 目标目录
     * @Description:
     * @date: 2024/8/4
     * @return: java.lang.String
     */
    private static String getZipNameOfFile(String src, String tar) {
        // temp.txt --> temp.zip
        return tar + src.substring(0, src.lastIndexOf(".")) + SUFFIX;
    }

    /**
     * 获取目录压缩包名
     *
     * @param src 源目录
     * @param tar 目标目录
     * @Description:
     * @date: 2024/8/4
     * @return: java.lang.String
     */
    private static String getZipNameOfDir(String src, String tar) {
        // temp --> temp.zip
        return tar + (isEmpty.test(tar) ? EMPTY : File.separator) + src + SUFFIX;
    }

    /**
     * 将文件写入zip流
     *
     * @param zipOut zip输出流
     * @param path   文件路径
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void writeToZip(ZipOutputStream zipOut, Path path) {
        // 写入目录
        if (Files.isDirectory(path)) {
            try {
                zipOut.putNextEntry(new ZipEntry(path.toString() + File.separator));
                zipOut.closeEntry();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }

        // 写入文件
        try (BufferedInputStream input = new BufferedInputStream(Files.newInputStream(path))) {
            zipOut.putNextEntry(new ZipEntry(path.toString()));     // 添加进入点

            // 输出流数据
            int len;
            final byte[] buff = new byte[BUFFER_SIZE];
            while (!Objects.equals(len = input.read(buff), EOF)) {
                zipOut.write(buff, 0, len);
            }
            zipOut.closeEntry();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解压文件/目录
     *
     * @param zipFilePath 压缩文件路径
     * @param tar         保存目录
     * @Description:
     * @date: 2024/8/4
     * @return: boolean
     */
    public static boolean unzip(String zipFilePath, String tar) throws IOException {
        // 如果存在tar,添加文件分隔符
        if (!isEmpty.test(tar)) tar += File.separator;

        // 创建压缩输入流
        try (ZipInputStream zipIn = new ZipInputStream(
                new BufferedInputStream(Files.newInputStream(Paths.get(zipFilePath))))) {
            ZipFile zipFile = new ZipFile(zipFilePath);
            ZipEntry entry;

            // 遍历压缩包文件
            while (Objects.nonNull(entry = zipIn.getNextEntry())) {
                // 如果是目录,创建目录
                if (isDirectory(entry)) {
                    Files.createDirectories(Paths.get(tar + entry.getName()));
                    continue;
                }

                // 读取文件
                createParentIfNotExists(tar, entry);
                readFromZip(tar, zipFile, entry);
            }
        }
        return true;
    }

    /** 判断是否为目录
     * @Description:
     * @date: 2024/8/4
     * @param entry 进入点
     * @return: boolean
     */
    private static boolean isDirectory(ZipEntry entry) {
        return entry.getName().endsWith(File.separator);
    }

    /**
     * 如果父目录不存在,创建父目录
     *
     * @param tar   目标目录
     * @param entry Zip流进入点
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void createParentIfNotExists(String tar, ZipEntry entry) throws IOException {
        Path parent = Paths.get(entry.getName()).getParent();                   // 获取当前父目录
        parent = Paths.get(tar + (Objects.isNull(parent) ? EMPTY : parent));    // 获取完整父目录
        Files.createDirectories(parent);                                        // 创建目录
    }

    /**
     * 从zip流读文件
     *
     * @param tar     目标目录
     * @param zipFile 压缩文件
     * @param entry   进入点
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void readFromZip(String tar, ZipFile zipFile, ZipEntry entry) throws IOException {
        tar += entry.getName(); // 保存文件路径
        try (BufferedInputStream input = new BufferedInputStream(zipFile.getInputStream(entry));
             BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(Paths.get(tar)))) {
            int len;
            final byte[] buff = new byte[BUFFER_SIZE];
            while (!Objects.equals(len = input.read(buff), EOF)) {
                output.write(buff, 0, len);
            }
        }
    }

    /**
     * 断言 <code>condition == true</code>,
     * 如果<code>condition == false</code>, 抛出异常
     *
     * @param cls       异常类
     * @param condition 断言条件
     * @param message   <code>condition == false</code>时的异常信息
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void require(Class<? extends Throwable> cls, boolean condition, String message) {
        try {
            if (!condition)
                throw cls.getConstructor(String.class).newInstance(message);
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }
}


 二、测试

   Main

package com.rocketmq.test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * @Create: 2024/7/31
 * @Description:
 * @FileName: Main
 */
public class Main {
    public static void main(String[] args) throws IOException {
        Consumer<String> print = text ->
                System.out.println(LocalDateTime.now() + ": " + text);

        print.accept("项目根目录:" + System.getProperty("user.dir") + "\n\n");
        Path path = Paths.get("temp");
        if (Files.exists(path)) deleteDir(path);

        print.accept("创建临时文件夹...");
        createTempDir(path, 5, 2);
        print.accept("创建完成. [" + path + "]\n");

        print.accept("开始压缩文件...");
        ZipUtil.zip(path.toString(), "");
        print.accept("压缩完成. [" + path + ".zip" + "]\n");

        print.accept("开始解压文件...");
        ZipUtil.unzip(path.toString() + ".zip", "dir");
        print.accept("解压完成. [dir]\n");

        print.accept("压缩解压后文件大小是否相等:" +
                Objects.equals(Files.size(path), Files.size(Paths.get("dir" + File.separator + path))));
    }

    /**
     * 删除文件夹及文件
     *
     * @param path 文件夹路径
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void deleteDir(Path path) throws IOException {
        Consumer<Path> consumer = p -> {
            try {
                Files.deleteIfExists(p);
            } catch (IOException e) {
                e.printStackTrace();
            }
        };
        Files.walk(path)                            // 遍历文件夹
                .sorted(Comparator.reverseOrder())  // 逆序排序(文件在前,文件夹在后)
                .forEach(consumer);                 // 删除文件/文件夹
    }

    /**
     * @param root
     * @param dirNum
     * @param fileNum
     * @Description:
     * @date: 2024/8/4
     * @return: void
     */
    private static void createTempDir(Path root, int dirNum, int fileNum) throws IOException {
        Files.createDirectories(root);

        for (int i = 0; i < dirNum || i < fileNum; i++) {
            String name = randomName(root.toString());
            if (Math.random() > 0.4) createTempDir(Paths.get(name), dirNum - 1, fileNum - 1);
            else createTempFile(name + ".txt");
        }
    }

    private static void createTempFile(String filename) throws IOException {
        Path path = Paths.get(filename);
        Files.deleteIfExists(path);
        Files.write(path, "Hello World".getBytes(),
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
    }

    private static String randomName(String prefix) {
        return prefix + File.separator + System.currentTimeMillis();
    }
}


总结

        通过对Zip流的学习和代码编写,了解到Zip流最重要的一点是 putNextEntry 方法,这个方法定位了文件/文件夹在Zip流中的位置。

        在编写也有一些问题,如 ZipEntry isDirectory() 方法以 "/" 来判断是否为目录,而不是File.seperator

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值