Java 压缩与解压zip文件


一、压缩文件大致可以分为三种:ZIP、JAR、GZ。

压缩流

      在日常中经常会使用到像WinRAR或WinZIP这样的压缩文件,通过这些软件可以把一个很大的文件进行压缩以方便传输。

     在JAVA中,为了减少传输时的数据量也提供了专门的压缩流,可以将文件或文件夹压缩成 ZIP、JAR、GZIP等文件的格式。
 

压缩流的实现:正常情况下在IO操作中,所有的类库都是在io包中。

在JAVA IO中,不仅可以实现ZIP压缩格式的输入、输出,也可以实现ZIP、JAR及GZIP文件格式的压缩:

JAR压缩的支持类保存在java.util.jar包中,常用的类有如下几个:
     JAR压缩输出流:JarOutputStream
     JAR压缩输入流:JarInputStream
     JAR文件:JARFile
     JAR实体:JAREntry


GZIP是用于UNIX系统的文件压缩,在Linux中经常会使用到*.gz的文件,就是GZIP格式,GZIP压缩的支持类保存在java.util.zip包中,常用的类有如下几个:
     GZIP压缩输出流:GZIPOutputStream
     GZIP压缩输入流:GZIPInputStream
 

ZIP是一种较为常见的压缩形式,在Java中要想实现ZIP的压缩需要导入java.util.zip包,可以使用此包中的ZipFile、ZipOutputStream、ZipInputStream、ZipEntry几个类完成。

 

1、ZipOutputStream类

        ZipOutputStream是OutputStream的子类,该类实现了以ZIP文件格式写入文件的输出流过滤器。 包括对压缩和未压缩条目的支持。

2、ZipInputStream类

        ZipInputStream是InputStream的子类,该类实现了以ZIP文件格式读取文件的输入流过滤器。 包括对压缩和未压缩条目的支持。

    • protected ZipEntrycreateZipEntry(String name)

      为指定的条目名称创建一个新的 ZipEntry对象。

      ZipEntrygetNextEntry()

      读取下一个ZIP文件条目,并将流定位在条目数据的开头。如果没有更多条目,则为null 

3、ZipFile类:此类用于从zip文件读取条目。

        在Java中,ZipFile是一个专门表示zip压缩文件(.zip)的类每一个压缩文件都可以使用 ZipFile表示,还可以使用 ZipFile根据压缩后的文件名找到每一个压缩文件中的ZipEntry对象并将其进行解压缩操作。

        ZipFile在实例化的时候必须接收File类的实例,此File类的实例是指向一个压缩的*.zip文件。

    • ZipFile(File file)

      打开一个ZIP文件,读取指定的File对象。

      ZipFile(String name)

      打开一个zip文件进行阅读。

4、ZipEntry类:此类用于表示ZIP文件条目。

        在Java中,每一个压缩文件(.zip)中都会存在多个子文件,那么每一个子文件使用ZipEntry对象表示。可以获取到文件条目的数据大小,条目名称等文件条目信息

        ZipEntry在实例化的时候必须要设置名称,此名称实际上就是压缩文件中的每一个文件条目的名称。

    • ZipEntry(String name)

      创建具有指定名称的新的zip条目。

    • StringgetComment()

      返回条目的注释字符串。

      longgetCrc()

      返回未压缩条目数据的CRC-32校验和。

      StringgetName()

      返回条目的名称。

      longgetSize()

      返回条目数据的未压缩大小。

      longgetTime()

      返回条目的最后修改时间。

      voidsetTime(long time)

      设置条目的最后修改时间。


二、自定义ZipUtil工具类

包含三个方法:

    1、压缩一个文件

    2、压缩一个文件夹(嵌套或不嵌套都可以)

       1)递归实现嵌套文件或不嵌套文件夹的压缩 

        对一个文件夹进行压缩,例如,现在在某盘存在一个a文件夹。从使用各种压缩软件的经验来看,如果现在要进行压缩的话,则在压缩之后的文件中应该存在一个a文件夹。在文件夹中应该存放着各个压缩文件。所以在实现的时候就应该列出文件夹中的全部内容,并把每一个内容设置成ZipEntry的对象,保存到压缩文件之中。
   

 

    3、解压zip文件(里面嵌套或不嵌套都可以)到指定目录下

直接上代码


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

public class ZipUtil {
    private ZipUtil(){}
    /**
     * 压缩一个文件
     *
     * @param file    要压缩的文件
     * @param zipFile 压缩后的zip文件
     * @return boolean  true-压缩成功,false-压缩失败
     */
    public static boolean zipOneFile(File file, File zipFile) {
        try (
                InputStream input = new FileInputStream(file);
                ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
        ) {
            // 压缩读写之前必须通过putNextEntry设置一个ZipEntry对象(表示ZIP文件条目)
            zipOut.putNextEntry(new ZipEntry(file.getName()));
            //zipOut.setComment("UTF-8"); // 设置ZIP文件注释
            byte[] buffer = new byte[2048];
            int len = -1;
            while ((len = input.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 压缩一个文件夹(嵌套或不嵌套都可以)
     *
     * @param fileDir 要压缩的文件夹
     * @param zipFile 压缩后的zip文件
     * @return boolean  true-压缩成功,false-压缩失败
     */
    public static boolean zipFileDir(File fileDir, File zipFile) {
        // 检查目录是否存在,不存在时创建
        if (!fileDir.isDirectory()) {
            fileDir.mkdirs();
        }
        try (
                ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
        ) {
            File[] files = fileDir.listFiles();
            for (File fileSrc : files) {
                recursionZip(fileSrc, zipOut, fileDir.getName());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 递归实现文件或文件夹的压缩
     *
     * @param file     要压缩的文件或目录
     * @param zipOut   压缩后的zip文件输出流
     * @param filePath 要压缩的文件路径或目录路径(最后不带/)
     * @throws Exception
     */
    private static void recursionZip(File file, ZipOutputStream zipOut, String filePath) throws Exception {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File fileSrc : files) {
                recursionZip(fileSrc, zipOut, filePath + File.separator + file.getName());
            }
        } else {
            InputStream input = new FileInputStream(file);
            zipOut.putNextEntry(new ZipEntry(filePath + File.separator + file.getName()));
            byte[] buffer = new byte[2048];
            int len = -1;
            while ((len = input.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }
            input.close();
        }
    }

    /**
     * 解压zip文件(里面嵌套或不嵌套都可以)到指目录下
     *
     * @param file:需要解压的zip文件
     * @param destDir:将zip文件解压到某个路径下(最后不带/)
     * @throws Exception
     */
    public static void unZipFile(File file, String destDir) throws Exception {
        ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file)); // 实例化压缩输入流
        ZipFile zipFile = new ZipFile(file);  // 实例化ZipFile对象
        ZipEntry zipEntry = null;
        File outFile = null;
        OutputStream out = null;
        InputStream input = null;
        try {
            while ((zipEntry = zipInput.getNextEntry()) != null) {
                //System.out.println("解压文件: " + zipEntry.getName()); // 解压文件: file01\1.png
                outFile = new File(destDir + File.separator + zipEntry.getName());

                if(zipEntry.isDirectory()){
                    outFile.delete();
                    outFile.mkdirs();
                    continue;
                }
                if (outFile.exists()) {
                    outFile.delete();
                }else{
                    outFile.createNewFile();
                }

                input = zipFile.getInputStream(zipEntry); // 得到每一个压缩实体对象的输入流
                out = new FileOutputStream(outFile); // 实例化文件输出流
                byte[] buffer = new byte[2048];
                int len = -1;
                while ((len = input.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            }
        } finally {
            if (zipInput != null) zipInput.close();
            if (out != null) out.close();
            if (input != null) input.close();
            if (zipFile != null) zipFile.close();
        }
    }


    public static void main(String[] args) {
//        File file = new File("E:/java/text.txt");
//        File zipFile = new File("E:/java/text.zip");
//        boolean flag = ZipUtil.zipOneFile(file, zipFile);
//        System.out.println(flag);

//        File fileDir = new File("E:/java/file01");
//        File zipFile = new File("E:/java/file01.zip");
//        boolean flag = ZipUtil.zipFileDir(fileDir,zipFile);
//        System.out.println(flag);

        File zipFile = new File("E:/java/file01.zip");
        try {
            ZipUtil.unZipFile(zipFile, "E:/java/unzip");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

     本人亲测OK,但是解压只能是xx.zip文件下只能是一个文件夹条目,它里面嵌套包含ok,

      

有时我们可能拿到的是文件的 二进制数据,通过二进制流包装一一下,就可以了。比如:

    @PostMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile file) {
        try (
                ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream(), Charset.forName("GBK"));
        ) {
            ZipEntry zipEntry = null;
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                if (zipEntry.isDirectory()) {
                    System.out.println("==目录==" + zipEntry.getName());
                } else {
                    System.out.println("==文件==" + zipEntry.getName());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return "error";
        }
        return "success";
    }

    @GetMapping("/download")
    public ResponseEntity<byte[]> download() {
        List<BytesFileItem> bytesFileItemList = getBytesFileItemList();

        try (
                ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
                ZipOutputStream zipOut = new ZipOutputStream(byteArrayOut);
        ) {
            ZipEntry zipEntry = null;
            for (BytesFileItem fileItem : bytesFileItemList) {
                zipEntry = new ZipEntry(fileItem.getName());
                zipEntry.setSize(fileItem.getContent().length);

                zipOut.putNextEntry(zipEntry);
                zipOut.write(fileItem.getContent());
            }
            zipOut.close();

            String downloadFilenName = "下载嵌套zip文件.zip";
            HttpHeaders headers = new HttpHeaders();
            String downloadFileName = new String(downloadFilenName.getBytes("UTF-8"), "iso-8859-1");
            headers.setContentDispositionFormData("attachment", downloadFileName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(byteArrayOut.toByteArray(), headers, HttpStatus.CREATED);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return  ResponseEntity.ok(new byte[0]);
    }

    //模拟下载嵌套文件
    public List<BytesFileItem> getBytesFileItemList() {
        List<BytesFileItem> list = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {
            // m模拟文件名和嵌套文件目录
            String fileName = (i % 2) == 0 ? "100_" + i + ".txt" : "200_" + i + ".png";
            int firstDir = i % 2;
            String secondDir = String.valueOf(firstDir) + File.separator + "目录dir" + i % 3;

            BytesFileItem bytesFileItem = new BytesFileItem();
            bytesFileItem.setName(secondDir + File.separator + fileName);
            bytesFileItem.setContent(("==模拟下载嵌套文件==i=" + i).getBytes(StandardCharsets.UTF_8));
            list.add(bytesFileItem);
        }
        return list;
    }

    class BytesFileItem {

        /**
         * 文件名
         */
        private String name;

        /**
         * 文件内容
         */
        private byte[] content;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public byte[] getContent() {
            return content;
        }

        public void setContent(byte[] content) {
            this.content = content;
        }
    }

注意:

  • 各种流之间的灵活使用。
  • 如果出现中文名报错,可知指定流的编码。
  • 有时会报下载文件被损坏错误,肯定就是因为下载文件时文件没有读取完全。

对于影像、流等应该按照字节来读取,而不应该一下读取多个字节,这样容易少读取文件,造成文件损坏。

 

总结:

1、压缩文件

    将要压缩的文件作为输入流,将压缩后的 .zip文件作为 ZipOutputStream输出流,进行读写操作即可,

    但是,压缩读写之前必须通过 ZipOutputStream类的 putNextEntry方法设置一个ZipEntry对象。

    ZipEntry对象:表示ZIP文件条目,一般取要压缩的文件名(xxx.后缀),保持一致。

2、解压zip文件

    将 .zip文件作为 ZipInputStream输入流,将.zip文件解压到某个路径下作为输出文件的根路径,

    通过输入流获取文件条目 zipEntry对象,然后,根据zipEntry对象信息在根路径在创建目录和文件(与压缩文件名保持一致),最后读写即可。

3、具体对象和方法使用,查看API。

 

参考文章:

    掌握压缩流的使用(ZipOutputStream、ZipFile、ZipInputStream)[java.util包中]

    解决ZipOutputStream压缩文件中文名乱码问题

    中文乱码的问题都是基于JDK6,但是在JDK7中已经解决了,只要把JDK版本升到7就可以。

 

    站在前辈的肩膀上,每天进步一点点

ends~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值