使用Java实现多个文件压缩打包
来源:互联网 发布:怎么把淘宝店做好 编辑:程序博客网 时间:2024/05/16 12:00
引言
在做项目的时候经常会涉及到文件的压缩,比如近期用Java Web做一个后台管理,后台有个导出功能,需要统计生成几十个excel文件,然后进行下载,如果不将这些文件进行压缩传送,耗费用户流量不说,用户浏览器还会一个接一个地接收文件不停的点击确认保存。所以需要对文件进行压缩传送。
关于文件压缩,Java的java.util.zip包提供了这个功能。这个包下总共有20多个相关的类,下面介绍一下常用的几个类,读者感兴趣也可查看该工具包。
该工具包下和解压缩相关的基类有ZipInputStream、ZipOutputStream、GZIPInputStream、GZIPOutputStream。其中ZipInputStream、GZIPInputStream这两个输入流主要用于解压时读取压缩文件,ZipOutputStream、GZIPOutputStream两个输出流用于压缩,将数据流写入压缩文件。通过名字我们可以看到这两个类对应我们常见的以.zip和.gzip结尾的压缩文件类型。另外还有一个ZipFile,这个类会在解压时用到。个人理解是用于初始化解析File。
关于压缩,首先贴上实现的压缩代码
/** * Created by ltaoj on 2017/10/16. */public class IOUtil { /** * 通过指定路径和文件名来获取文件对象,当文件不存在时自动创建 * @param path * @param fileName * @return * @throws IOException */ public static File getFile(String path, String fileName) throws IOException { // 创建文件对象 File file; if (path != null && !path.equals("")) file = new File(path, fileName); else file = new File(fileName); if (!file.exists()) { file.createNewFile(); } // 返回文件 return file; } /** * 获得指定文件的输出流 * @param file * @return * @throws FileNotFoundException */ public static FileOutputStream getFileStream(File file) throws FileNotFoundException { return new FileOutputStream(file); } /** * 将多个文件压缩 * @param fileList 待压缩的文件列表 * @param zipFileName 压缩文件名 * @return 返回压缩好的文件 * @throws IOException */ public static File getZipFile(List<File> fileList, String zipFileName) throws IOException { File zipFile = getFile(PathConfig.getPath(), zipFileName); // 文件输出流 FileOutputStream outputStream = getFileStream(zipFile); // 压缩流 ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream); int size = fileList.size(); // 压缩列表中的文件 for (int i = 0;i < size;i++) { File file = fileList.get(i); zipFile(file, zipOutputStream); } // 关闭压缩流、文件流 zipOutputStream.close(); outputStream.close(); return zipFile; } /** * 将文件数据写入文件压缩流 * @param file 带压缩文件 * @param zipOutputStream 压缩文件流 * @throws IOException */ private static void zipFile(File file, ZipOutputStream zipOutputStream) throws IOException { if (file.exists()) { if (file.isFile()) { FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); ZipEntry entry = new ZipEntry(file.getName()); zipOutputStream.putNextEntry(entry); final int MAX_BYTE = 10 * 1024 * 1024; // 最大流为10MB long streamTotal = 0; // 接收流的容量 int streamNum = 0; // 需要分开的流数目 int leaveByte = 0; // 文件剩下的字符数 byte[] buffer; // byte数据接受文件的数据 streamTotal = bis.available(); // 获取流的最大字符数 streamNum = (int) Math.floor(streamTotal / MAX_BYTE); leaveByte = (int) (streamTotal % MAX_BYTE); if (streamNum > 0) { for (int i = 0;i < streamNum;i++) { buffer = new byte[MAX_BYTE]; bis.read(buffer, 0, MAX_BYTE); zipOutputStream.write(buffer, 0, MAX_BYTE); } } // 写入剩下的流数据 buffer = new byte[leaveByte]; bis.read(buffer, 0, leaveByte); // 读入流 zipOutputStream.write(buffer, 0, leaveByte); // 写入流 zipOutputStream.closeEntry(); // 关闭当前的zip entry // 关闭输入流 bis.close(); fis.close(); } } }}
代码比较好懂,就是循环遍历待压缩文件列表,将每个文件写入到压缩流。
主要看下private static void zipFile(File file, ZipOutputStream zipOutputStream) throws IOException
这个压缩的核心代码方法签名。可以看到在该方法中我们首先实例化了一个ZipEntry 对象,之后就是将zipEntry对象作为参数并调用了ZipOutputStream的putNextEntry()方法,接下来读取File的数据流并写入压缩流,通过循环将数据流写入压缩流完毕后,就调用了zipOutputStream的closeEntry()方法关闭当前的ZipEntry。
用Java实现文件压缩功能到此就实现完毕了,很简单。但是这里我比较好奇究竟是怎么进行压缩的,于是就寻着ZipOutputStream的方法调用去查个究竟。下面是ZipOutputStream的代码实现,putNextEntry方法和write方法:
/** * 开始一个新的Zip入口。如果当前入口是打开状态则关闭。把新入口作为当前的压缩流入口 * 如果没有指定压缩方法,则会使用默认的压缩方法。 * 如果没有指定文件修改时间,则会使用系统当前时间。 * @param e 将要写入压缩流的入口 * @exception ZipException 如果发生了压缩错误抛出此异常 * @exception IOException 如果发生了IO错误抛出此异常 */ public void putNextEntry(ZipEntry e) throws IOException { ensureOpen(); // 确保压缩流为打开状态 if (current != null) { closeEntry(); // 关闭之前的入口 } if (e.xdostime == -1) { // 如果没有文件修改时间,则使用当前系统时间 e.setTime(System.currentTimeMillis()); } if (e.method == -1) { e.method = method; // 使用默认压缩方法,有DEFAULT和STORED两种 } // 存储大小, 压缩后大小, crc-32 in LOC header e.flag = 0; switch (e.method) { case DEFLATED: // store size, compressed size, and crc-32 in data descriptor // immediately following the compressed entry data if (e.size == -1 || e.csize == -1 || e.crc == -1) e.flag = 8; break; case STORED: // 使用STORED压缩方法,压缩后大小、压缩前大小和crc-32一定被设置过。 if (e.size == -1) { e.size = e.csize; } else if (e.csize == -1) { e.csize = e.size; } else if (e.size != e.csize) { throw new ZipException( "STORED entry where compressed != uncompressed size"); } if (e.size == -1 || e.crc == -1) { throw new ZipException( "STORED entry missing size, compressed size, or crc-32"); } break; default: throw new ZipException("unsupported compression method"); } if (! names.add(e.name)) { throw new ZipException("duplicate entry: " + e.name); } if (zc.isUTF8()) e.flag |= EFS; current = new XEntry(e, written); // 使用XEntry将ZipEntry包裹 xentries.add(current); // 将该入口添加到Vector<XEntry> writeLOC(current); // 添加头部信息 } /** * 将byte数组写到入口,即将从数据流读到的数据写入压缩流 * @param b byte数组 * @param off byte数组的偏移 * @param len 要写入的大小 * @exception ZipException if a ZIP file error has occurred * @exception IOException if an I/O error has occurred */ public synchronized void write(byte[] b, int off, int len) throws IOException { ensureOpen(); if (off < 0 || len < 0 || off > b.length - len) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } if (current == null) { throw new ZipException("no current ZIP entry"); } ZipEntry entry = current.entry; switch (entry.method) { case DEFLATED: // 默认是DEFLATED,所以会首先调用父类的write方法。 super.write(b, off, len); break; case STORED:// 如果数据本来是压缩的,那么就将拿到的数据流直接写到压缩流了 written += len; if (written - locoff > entry.size) { throw new ZipException( "attempt to write past end of STORED entry"); } out.write(b, off, len); break; default: throw new ZipException("invalid compression method"); } crc.update(b, off, len); }
从上边的write方法中我们看到其实是调用了父类DeflaterOutputStream的write方法:
public void write(byte[] b, int off, int len) throws IOException { if (def.finished()) { throw new IOException("write beyond end of stream"); } if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } if (!def.finished()) { // 判断已经压缩的数据输出流是否结束 def.setInput(b, off, len); // 设置压缩需要的输入数据,调用该方法其实并没有 做什么事情,只是简单的进行赋值 while (!def.needsInput()) { // 循环判断是否需要输入数据,根据len的值是否小于等于0. deflate(); // 这个才是压缩的核心方法 } } }
通过以上代码,我们看到deflate()方法才是压缩的核心代码,该方法最终会通过JNI本地调用deflateBytes方法来进行压缩,该方法或将压缩后的字节大小返回,因为数据流byte是引用类型,所以通过该压缩方法处理后会变成压缩后的数据,有了压缩后的数据以及大小,接下来就可以通过out.write(buf, 0, len);将压缩后的数据写入压缩流了。所以关于数据压缩的核心代码在Java中是看不到了~
- 使用Java实现多个文件压缩打包
- JAVA实现多文件压缩打包
- java实现多文件压缩打包
- java实现文件打包、压缩和解压缩
- 使用PHP的ZipArchive类实现多个文件的zip压缩包打包下载
- 使用SharpZipLib压缩打包多个内存中的文件
- 使用C#语言进行多个文件的压缩打包
- java实现多个文件压缩
- java实现多个文件压缩
- java实现多个文件压缩
- java实现多个文件打包下载
- java实现文件打包压缩处理
- java 压缩打包文件的实现方法
- java实现单个文件多个文件的zip压缩
- 【Java】Java实现zip压缩多个文件下载
- JAVA多个文件压缩
- Java实现将多个文件打包压缩成tar.gz文件
- java web 实现多个文件压缩下载
- linux之系统日志的查看和修改
- springboot系列(第三讲)
- 从零开始理解caffe网络的参数
- AS启动后卡在Refreshing gradle project的解决方法
- kubernetes创建资源对象yaml文件例子--pod
- 使用Java实现多个文件压缩打包
- leetcode.array--63. Unique Paths II
- 假身份证
- struts2初始
- JPA JPQL/持久化查询语言
- 刷题笔记:C/C++工程师能力评估3
- java异常类 Object类
- 成长管理(笔记)
- 表格设计