【语言】压缩 20M 文件从 30 秒到 2 秒的全部过程?

有一个需求需要将前端传过来的 10 张照片,然后后端进行处理以后压缩成一个压缩包通过网络流传输出去。之前没有接触过用 Java 压缩文件的,所以就直接上网找了一个例子改了一下用了,改完以后也能使用,但是随着前端所传图片的大小越来越大的时候,耗费的时间也在急剧增加,最后测了一下压缩 20M 的文件竟然需要 30 秒的时间。压缩文件的代码如下。

 public static void zipFileNoBuffer() {
  File zipFile = new File(ZIP_FILE);
 try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile))) {
    //开始时间
    long beginTime = System.currentTimeMillis();

    for (int i = 0; i < 10; i++) {
        try (InputStream input = new FileInputStream(JPG_FILE)) {
            zipOut.putNextEntry(new ZipEntry(FILE_NAME + i));
            int temp = 0;
            while ((temp = input.read()) != -1) {
                zipOut.write(temp);
            }
        }
    }
    printInfo(beginTime);
} catch (Exception e) {
    e.printStackTrace();
}
}

复制代码这里找了一张 2M 大小的图片,并且循环十次进行测试。打印的结果如下,时间大概是 30 秒。
fileSize:20M
consum time:29599

优化过程 - 从 30 秒到 2 秒

进行优化首先想到的是利用缓冲区 BufferInputStream。在 FileInputStream 中 read() 方法每次只读取一个字节。源码中也有说明。

/Reads a byte of data from this input stream. This method blocks

if no input is yet available.
 
 @return     the next byte of data, or <code>-1</code> if the end of the
       file is reached.
 @exception  IOException  if an I/O error occurs./

public native int read() throws IOException;

复制代码这是一个调用本地方法与原生操作系统进行交互,从磁盘中读取数据。每读取一个字节的数据就调用一次本地方法与操作系统交互,是非常耗时的。例如我们现在有 30000 个字节的数据,如果使用 FileInputStream 那么就需要调用 30000 次的本地方法来获取这些数据,而如果使用缓冲区的话(这里假设初始的缓冲区大小足够放下 30000 字节的数据)那么只需要调用一次就行。因为缓冲区在第一次调用 read() 方法的时候会直接从磁盘中将数据直接读取到内存中。随后再一个字节一个字节的慢慢返回。

BufferedInputStream 内部封装了一个 byte 数组用于存放数据,默认大小是 8192

优化过后的代码如下:

public static void zipFileBuffer() {

File zipFile = new File(ZIP_FILE);
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(zipOut)) {
    //开始时间
    long beginTime = System.currentTimeMillis();
    for (int i = 0; i < 10; i++) {
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(JPG_FILE))) {
            zipOut.putNextEntry(new ZipEntry(FILE_NAME + i));
            int temp = 0;
            while ((temp = bufferedInputStream.read()) != -1) {
                bufferedOutputStream.write(temp);
            }
        }
    }
    printInfo(beginTime);
} catch (Exception e) {
    e.printStackTrace();
}
}

复制代码输出
——Buffer
fileSize:20M
consum time:1808