Java压缩与解压缩ZIP文件_java解压缩zip文件-程序员宅基地

技术标签: zip  java  解压缩文件  ZipOutputStream  笔记杂谈  压缩文件  

前言

在现代计算机上,数据传输和存储越来越依赖于文件压缩技术。当我们需要发送大量数据时,压缩文件可以大大减少传输时间和网络带宽,而且压缩文件还可以帮助我们节省磁盘空间。在Java中提供了压缩和解压缩文件的功能,可以使用java.util.zip包中的类来实现。本篇将对如何使用 Java 实现单文多件压缩和解压缩进行总结。

文件压缩指的是将一个或多个文件通过压缩算法,将其存储为一个更小的文件,以便于存储和传输。压缩的原理是通过对文件的数据进行编码和压缩,使其占用更少的空间。压缩后的文件可以通过解压缩算法还原成原始的文件格式。在文件压缩过程中,常见的操作是将多个文件打包成一个压缩文件,例如zip、tar等格式。

Java解压缩文件

常见的文件压缩格式包括:

  • ZIP:最常见的压缩文件格式之一,可以存储一个或多个文件,并可在不同的操作系统中进行解压缩。
  • TAR:Linux系统中的常见文件压缩格式,通常用于打包多个文件,但不会进行压缩。
  • GZIP:常用的文件压缩格式,通常用于压缩单个文件,可以获得更高的压缩比。
  • BZIP2:高效的压缩算法,通常用于压缩文本文件和XML文件等。
  • JAR: Jar包对于Java开发同学来说肯定很熟悉,其也是一个压缩包

Java提供了多种用于压缩和解压缩文件的API,主要包括以下类和方法:

  • ZipOutputStream 和 ZipInputStream:用于创建和读取ZIP格式的压缩文件。
  • GZIPOutputStream 和 GZIPInputStream:用于创建和读取GZIP格式的压缩文件。
  • JarOutputStream 和 JarInputStream:用于创建和读取JAR格式的压缩文件。
  • DeflaterOutputStream 和 InflaterInputStream:用于创建和读取DEFLATE格式的压缩文件。
  • CheckedOutputStream 和 CheckedInputStream:用于在压缩和解压缩过程中计算文件的校验和。

压缩和解压缩ZIP文件

通过使用Java 自带的 java.util.zip 类库下的ZipOutputStreamZipInputStreamZipEntry实现文件的压缩和解压缩,其中ZipOutputStream用于创建ZIP压缩文件输出流输出ZIP压缩文件,ZipInputStream用于创建ZIP文件输入流读取ZIP文件用于解压缩而ZipEntry对应ZIP压缩包中的每个被压缩对象;

  • 生成ZIP文件

    压缩单个文件或者单个文件夹方法,代码如下:

    /**
     * 压缩文件(支持单个文件和单个文件夹)
     * @param sourceFile 被压缩文件/文件夹
     * @param zipFile Zip文件
     */
    public static void zipCompress(File sourceFile, File zipFile) {
          
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
          
            // 设置压缩方法
            zos.setMethod(ZipOutputStream.DEFLATED);
            zos.setLevel(Deflater.BEST_COMPRESSION); // 默认为-1,压缩级别,1速度快,效率低,9 速度满,效率高
            // zos.setLevel(Deflater.BEST_SPEED);
            zos.setComment("zip文件说明");
            // 处理文件夹
            if (sourceFile.exists() && sourceFile.isDirectory() && Objects.nonNull(sourceFile.listFiles())){
          
                Arrays.stream(Objects.requireNonNull(sourceFile.listFiles())).forEach(file -> {
          
                    addZipFile(file, zos);
                });
            }else{
          
                addZipFile(sourceFile, zos);
            }
        } catch (IOException e) {
          
            e.printStackTrace();
        }
    }
    

    为了支持读取单个文件夹进行压缩,增加一个向ZipOutputStream中添加ZipEntry的方法,代码如下:

    /**
     * 向ZIP中添加文件
     * @param file 源文件
     * @param zos zip输出流
     */
    private static void addZipFile(File file, ZipOutputStream zos){
          
        if (!file.exists() || file.isDirectory()){
          
            throw new RuntimeException("文件不存在或该文件为文件夹,请检查");
        }
        try {
          
            // 读入文件
            FileInputStream fis = new FileInputStream(file);
            // 创建压缩对象并设置一些属性
            ZipEntry entry = new ZipEntry(file.getName());
            entry.setMethod(ZipEntry.DEFLATED); // 压缩方法默认为DEFLATED
            // entry.setMethod(ZipEntry.STORED); // STORED(不压缩)。当使用STORED压缩方法时,需要设置未压缩的数据大小和CRC-32校验和,否则压缩和解压缩时会出现错误。
            entry.setSize(file.length()); // 设置未压缩的数据大小,这里设置的是文件大小
            // 计算 CRC-32 校验码
            // byte[] data = Files.readAllBytes(file.toPath());
            // CRC32 crc = new CRC32();
            // crc.update(data);
            // entry.setCrc(crc.getValue()); // 设置CRC-32校验和,用于保证压缩后的数据完整性,尽量别手动设置,可以通过CRC-32计算
            entry.setCompressedSize(file.length()); // 设置压缩后的数据大小,这里设置的是使用DEFLATED方法压缩后的数据大小
            entry.setExtra(new byte[]{
          }); // 设置额外的数据,这里设置为空
            entry.setComment("file comment"); // 设置ZipEntry的注释,即文件说明
            entry.setCreationTime(FileTime.from(Instant.now())); // 设置文件的创建时间
            entry.setLastAccessTime(FileTime.from(Instant.now())); // 设置文件的最后访问时间
            entry.setLastModifiedTime(FileTime.from(Instant.now())); // 设置文件的最后修改时间。
            // 向ZIP输出流中添加一个ZIP实体,构造方法中的name参数指定文件在ZIP包中的文件名
            zos.putNextEntry(entry);
            // 向ZIP实体中写入内容
            byte[] buf = new byte[1024];
            int len;
            while ((len = fis.read(buf)) > 0) {
          
                zos.write(buf, 0, len);
            }
            // 关闭ZipEntry
            zos.closeEntry();
        } catch (IOException e) {
          
            e.printStackTrace();
        }
    }
    

    注意:

    涉及到文件IO流,如果没有使用try with source 的语法,一定要记得关闭输入输出流;

    使用Java.util.zip下的工具类压缩成ZIP不支持设置ZIP密码且每种模式下生成的ZIP文件大小大于等于原文件/文件夹;

    当使用STORED压缩方法时,需要设置未压缩的数据大小和CRC-32校验和,该值需要借助CRC-32计算非常的麻烦,不设置此值会抛出异常;

    对于压缩,可以使用 ZipOutputStream 的 putNextEntry 方法逐个添加文件,避免将所有文件一次性读入内存;

    可以设置缓冲区大小,例如对于文件的读取和写入,可以设置缓冲区大小为 4KB 或者 8KB,减少内存的占用;

    对于解压缩,可以使用 ZipInputStream 的 getNextEntry 方法逐个读取文件,避免将所有文件一次性读入内存;

  • 解压缩ZIP文件

    将ZIP文件解压缩支持生成文件夹,代码如下:

    /**
     * 解压缩ZIP文件
     * @param zipFile ZIP文件
     * @param destDir 目标路径
     */
    public static void zipDecompress(File zipFile, File destDir) {
          
        byte[] buffer = new byte[1024];
        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
          
            ZipEntry entry = zis.getNextEntry();
            while (entry != null) {
          
                File file = new File(destDir, entry.getName());
                if (entry.isDirectory()) {
          
                    file.mkdirs();
                } else {
          
                    File parent = file.getParentFile();
                    if (!parent.exists()) {
          
                        parent.mkdirs();
                    }
                    try (FileOutputStream fos = new FileOutputStream(file)) {
          
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
          
                            fos.write(buffer, 0, len);
                        }
                    }
                }
                entry = zis.getNextEntry();
            }
        } catch (IOException e) {
          
            e.printStackTrace();
        }
    }
    
  • 如何避免压缩文件中的注入攻击?

    压缩文件中的路径名和文件名可以被精心构造的攻击者利用,从而使得解压缩的过程中可能会导致路径遍历、文件覆盖等问题,进而导致安全问题。为了避免这些安全问题,可以进行如下处理:

    • 限制压缩文件中的路径名和文件名的长度,以及字符集,可以采用白名单机制对输入进行限制;
    • 对于解压缩的路径名和文件名,不要使用压缩文件中的路径名和文件名,而是在解压缩时自行构造一个路径名和文件名;
    • 对于不可信任的压缩文件,最好在安全的环境下解压缩,例如在沙箱或虚拟机中进行操作。

检验

主要测试使用上述代码压缩单文件和解压缩单文件能否成功以后测量单个文件的处理耗时,同时测试单文件夹多文件压缩和解压缩能否成功以及处理耗时,单元测试代码如下:

@Test
void testJavaUtilZip(){
    // 测试压缩和解压缩单文件ZIP
    // 被压缩的MP4单文件,大小112.5MB
    File inputFile = new File("/Users/zlc/Documents/own/images/GPT-4 Developer Livestream.mp4");
    // ZIP文件路径
    File zipFile = new File("/Users/zlc/Documents/own/mp4.zip");
    // ZIP 解压缩路径
    File unzipFile = new File("/Users/zlc/Documents/own/unzip");
    long start = System.currentTimeMillis();
    // 压缩文件
    ZipFileUtil.zipCompress(inputFile, zipFile);
    long end = System.currentTimeMillis();
    System.out.println("ZIP-压缩单文件耗时:" + (end - start) + "毫秒");
    start = System.currentTimeMillis();
    ZipFileUtil.zipDecompress(zipFile, unzipFile);
    end = System.currentTimeMillis();
    System.out.println("ZIP-解压缩单文件耗时:" + (end - start) + "毫秒");
    // 单文件夹多文件压缩和解压缩测试
    // 文件夹大小2.42G
    File inputFiles = new File("/Users/zlc/Documents/own/images");
    File zipFiles = new File("/Users/zlc/Documents/own/imagesZip.zip");
    File unzipFiles = new File("/Users/zlc/Documents/own/imagesUnzip");
    start = System.currentTimeMillis();
    // 压缩文件
    ZipFileUtil.zipCompress(inputFiles, zipFiles);
    end = System.currentTimeMillis();
    System.out.println("ZIP - 多文件压缩耗时:" + (end - start) + "毫秒");
    start = System.currentTimeMillis();
    ZipFileUtil.zipDecompress(zipFiles, unzipFiles);
    end = System.currentTimeMillis();
    System.out.println("ZIP - 多文件解压缩耗时:" + (end - start) + "毫秒");
}

测试结果如下:

ZIP-压缩单文件耗时:5492毫秒
ZIP-解压缩单文件耗时:1920毫秒
ZIP - 多文件压缩耗时:136059毫秒
ZIP - 多文件解压缩耗时:45739毫秒

同时测试了设置不同压缩等级的耗时比较,结果如下:

// 不同压缩等级下的处理耗时 
BEST_COMPRESSION 2.43G 文件夹耗时119801毫秒快两分钟了
BEST_SPEED 2.43G 文件夹耗时112646毫秒 也没差多少,但确实快了

结论:

测试环境为MacOS 四核Intel Core i7,16G内存,处理将近2.5G大小的文件夹耗时接近两分钟,效率十分低下,在总文件大小不大的时候可以考虑使用JDK自带的压缩工具类。

应用

设计一个API,通过使用Hutool生成两个CSV文件和一个Excel文件,将这三个文件放入到ZIP压缩包中,当通过浏览器调用API时,下载ZIP压缩包。

同时在服务器上不生成CVS、EXCEL以及Zip文件而是直接通过HttpServletResponse将文件传送给客户端,避免服务器因过多生成这些文件导致服务硬盘不够用(PS:生成文件以后基本上不会有人管了,会随着时间的增加爆炸式增加,当然如果你需要留存建议生成本地文件存储到OSS中),代码如下:

  /**
 * 下载ZIP
 * @param response HttpServletResponse 响应流
 * @return zip file 
 */
@GetMapping(value = "/downloadZip")
public String downloadZipFile(HttpServletResponse response) {
    

    // 设置响应头
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=download.zip");

    try (ZipOutputStream outputStream = new ZipOutputStream(response.getOutputStream())) {
    

        ExcelWriter writer = new ExcelWriter(true);
        List<String> header = Arrays.asList("开始日期", "结束日期", "算法厂商", "期末资产总额", "累计成交金额",
                "期间委托笔数", "期间成交笔数", "期间撤单笔数");
        writer.writeHeadRow(header);

        List<List<String>> rows = new ArrayList<>();
        rows.add(Arrays.asList("张三", "里斯", "男", "张三", "里斯", "男", "张三", "里斯"));
        rows.add(Arrays.asList("李四", "王武", "女", "张三", "里斯", "男", "张三", "里斯"));
        writer.write(rows);

        writer.passRows(1);

        List<String> header1 = Arrays.asList("日期", "资金账号", "算法母单编号", "委托编号", "交易所", "股票代码",
                "委托数量", "交易方向", "订单类型", "委托价格", "委托状态", "累计成交数量", "累计成交金额");
        writer.writeHeadRow(header1);
        writer.autoSizeColumn(writer.getCurrentRow());
        List<List<String>> rows2 = new ArrayList<>();
        rows2.add(Arrays.asList("张三", "里斯", "男", "张三", "里斯", "男", "张三", "里斯", "找大大", "赵打打", "炸", "茅台", "米线"));
        rows2.add(Arrays.asList("李四", "王武", "女", "张三", "里斯", "男", "张三", "里斯", "找大大", "赵打打", "炸", "茅台", "米线"));
        writer.write(rows2);
        writer.autoSizeColumnAll();

        ZipEntry entry = new ZipEntry("数据.xlsx");
        outputStream.putNextEntry(entry);
        writer.flush(outputStream);
        writer.close();
        outputStream.closeEntry();

        ZipEntry entry1 = new ZipEntry("母单.csv");
        outputStream.putNextEntry(entry1);
        CsvWriter csvWriter1 = new CsvWriter(new OutputStreamWriter(outputStream));
        String[] csvHead1 = {
    "日期", "资金账号", "算法厂商", "算法", "算法母单编号", "交易所", "股票代码", "委托数量", "交易方向", "启动时间", "停止时间", "算法状态"};
        csvWriter1.writeLine(csvHead1);
        String[] csvData1 = {
    "20230203", "98830901", "XX", "TWAP", "12984", "SH", "600000", "10000", "4", "102311", "112311", "1"};
        String[] csvData2 = {
    "20230203", "98830901", "XX", "TWAP", "12984", "SH", "600000", "10000", "4", "102311", "112311", "1"};
        csvWriter1.writeLine(csvData1);
        csvWriter1.writeLine(csvData2);

        csvWriter1.flush();
        outputStream.closeEntry();

        ZipEntry entry2 = new ZipEntry("子单.csv");
        outputStream.putNextEntry(entry2);
        CsvWriter csvWriter2 = new CsvWriter(new OutputStreamWriter(outputStream));
        String[] csvHead2 = {
    "日期", "资金账号", "算法母单编号", "委托编号", "交易所", "股票代码", "委托数量", "交易方向", "订单类型", "委托价格", "委托状态", "累计成交数量", "累计成交金额"};
        csvWriter2.writeLine(csvHead2);
        String[] csvData3 = {
    "20230203", "98830901", "12984", "ord1122", "SH", "600000", "1000", "4", "1", "7.23", "1", "800", "5600"};
        String[] csvData4 = {
    "20230203", "98830901", "12984", "ord1122", "SH", "600000", "1000", "4", "1", "7.23", "1", "800", "5600"};
        csvWriter2.writeLine(csvData3);
        csvWriter2.writeLine(csvData4);
        csvWriter2.flush();
        outputStream.closeEntry();
        outputStream.finish();
        return "success";
    } catch (Exception e) {
    
        return "faild";
    }
}

总结

Java 自带的 java.util.zip 类库是一个基础的压缩和解压缩类库,它提供了很基本的压缩和解压缩功能。在处理小型文件或数据时,java.util.zip 是一个可行的选择。不过,对于大型文件或数据的处理,效率可能会受到影响。相比之下,一些第三方的类库如 Apache Commons Compress、Zip4j 等提供了更为高级的压缩和解压缩功能,同时也提供了更好的性能。
本文首发于香菜喵,打开微信随时随地读,文章下方 ↓ ↓ ↓

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chen15369337607/article/details/130591293

智能推荐

解决win10/win8/8.1 64位操作系统MT65xx preloader线刷驱动无法安装_mt65驱动-程序员宅基地

文章浏览阅读1.3w次。转载自 http://www.miui.com/thread-2003672-1-1.html 当手机在刷错包或者误修改删除系统文件后会出现无法开机或者是移动定制(联通合约机)版想刷标准版,这时就会用到线刷,首先就是安装线刷驱动。 在XP和win7上线刷是比较方便的,用那个驱动自动安装版,直接就可以安装好,完成线刷。不过现在也有好多机友换成了win8/8.1系统,再使用这个_mt65驱动

SonarQube简介及客户端集成_sonar的客户端区别-程序员宅基地

文章浏览阅读1k次。SonarQube是一个代码质量管理平台,可以扫描监测代码并给出质量评价及修改建议,通过插件机制支持25+中开发语言,可以很容易与gradle\maven\jenkins等工具进行集成,是非常流行的代码质量管控平台。通CheckStyle、findbugs等工具定位不同,SonarQube定位于平台,有完善的管理机制及强大的管理页面,并通过插件支持checkstyle及findbugs等既有的流..._sonar的客户端区别

元学习系列(六):神经图灵机详细分析_神经图灵机方法改进-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏27次。神经图灵机是LSTM、GRU的改进版本,本质上依然包含一个外部记忆结构、可对记忆进行读写操作,主要针对读写操作进行了改进,或者说提出了一种新的读写操作思路。神经图灵机之所以叫这个名字是因为它通过深度学习模型模拟了图灵机,但是我觉得如果先去介绍图灵机的概念,就会搞得很混乱,所以这里主要从神经图灵机改进了LSTM的哪些方面入手进行讲解,同时,由于模型的结构比较复杂,为了让思路更清晰,这次也会分开几..._神经图灵机方法改进

【机器学习】机器学习模型迭代方法(Python)-程序员宅基地

文章浏览阅读2.8k次。一、模型迭代方法机器学习模型在实际应用的场景,通常要根据新增的数据下进行模型的迭代,常见的模型迭代方法有以下几种:1、全量数据重新训练一个模型,直接合并历史训练数据与新增的数据,模型直接离线学习全量数据,学习得到一个全新的模型。优缺点:这也是实际最为常见的模型迭代方式,通常模型效果也是最好的,但这样模型迭代比较耗时,资源耗费比较多,实时性较差,特别是在大数据场景更为困难;2、模型融合的方法,将旧模..._模型迭代

base64图片打成Zip包上传,以及服务端解压的简单实现_base64可以装换zip吗-程序员宅基地

文章浏览阅读2.3k次。1、前言上传图片一般采用异步上传的方式,但是异步上传带来不好的地方,就如果图片有改变或者删除,图片服务器端就会造成浪费。所以有时候就会和参数同步提交。笔者喜欢base64图片一起上传,但是图片过多时就会出现数据丢失等异常。因为tomcat的post请求默认是2M的长度限制。2、解决办法有两种:① 修改tomcat的servel.xml的配置文件,设置 maxPostSize=..._base64可以装换zip吗

Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字-程序员宅基地

文章浏览阅读1k次,点赞17次,收藏22次。Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字

随便推点

ESXi 快速复制虚拟机脚本_exsi6.7快速克隆centos-程序员宅基地

文章浏览阅读1.3k次。拷贝虚拟机文件时间比较长,因为虚拟机 flat 文件很大,所以要等。脚本完成后,以复制虚拟机文件夹。将以下脚本内容写入文件。_exsi6.7快速克隆centos

好友推荐—基于关系的java和spark代码实现_本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。-程序员宅基地

文章浏览阅读2k次。本文主要实现基于二度好友的推荐。数学公式参考于:http://blog.csdn.net/qq_14950717/article/details/52197565测试数据为自己随手画的关系图把图片整理成文本信息如下:a b c d e f yb c a f gc a b dd c a e h q re f h d af e a b gg h f bh e g i di j m n ..._本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。

南京大学-高级程序设计复习总结_南京大学高级程序设计-程序员宅基地

文章浏览阅读367次。南京大学高级程序设计期末复习总结,c++面向对象编程_南京大学高级程序设计

4.朴素贝叶斯分类器实现-matlab_朴素贝叶斯 matlab训练和测试输出-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏12次。实现朴素贝叶斯分类器,并且根据李航《统计机器学习》第四章提供的数据训练与测试,结果与书中一致分别实现了朴素贝叶斯以及带有laplace平滑的朴素贝叶斯%书中例题实现朴素贝叶斯%特征1的取值集合A1=[1;2;3];%特征2的取值集合A2=[4;5;6];%S M LAValues={A1;A2};%Y的取值集合YValue=[-1;1];%数据集和T=[ 1,4,-1;..._朴素贝叶斯 matlab训练和测试输出

Markdown 文本换行_markdowntext 换行-程序员宅基地

文章浏览阅读1.6k次。Markdown 文本换行_markdowntext 换行

错误:0xC0000022 在运行 Microsoft Windows 非核心版本的计算机上,运行”slui.exe 0x2a 0xC0000022″以显示错误文本_错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行-程序员宅基地

文章浏览阅读6.7w次,点赞2次,收藏37次。win10 2016长期服务版激活错误解决方法:打开“注册表编辑器”;(Windows + R然后输入Regedit)修改SkipRearm的值为1:(在HKEY_LOCAL_MACHINE–》SOFTWARE–》Microsoft–》Windows NT–》CurrentVersion–》SoftwareProtectionPlatform里面,将SkipRearm的值修改为1)重..._错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行“slui.ex