使用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中是看不到了~

原创粉丝点击