《阿里云双十一活动,云服务器每月最低仅需7元》

来源:jianshu.com/p/25b328753017

  • 第一次优化过程-从30秒到2秒
  • 第二次优化过程-从2秒到1秒
    • 使用频道
    • 使用内存映射文件
    • 使用管道
  • 总结
  • 参考文章

有一个需求需要将前端传过来的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:20Mconsum time:29599

第一次优化过程-从30秒到2秒

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

/** * 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 -1 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内部封装了一个字节的用于存放数据,最小大小是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();    }}

输出

------BufferfileSize:20Mconsum time:1808

可以看到相比较于第一次使用FileInputStream效率已经提升了很多了

第二次优化过程-从2秒到1秒

使用buffer正规的话已经是满足了我的需求了,但是秉着学以致用的想法,就想着用NIO中知识进行优化一下。

使用频道

要用为什么Channel呢?因为在NIO新中出了Channel状语从句:ByteBuffer。正是因为它们的结构更加符合操作系统执行I / O的方式,所以其速度相比较于传统IO而言速度有了显着的提高。Channel就像一个包含着煤矿的矿藏,而ByteBuffer则是派遣煤矿藏的卡车。如此我们与数据的相互作用都是与ByteBuffer的相互作用。

在NIO能够中产生FileChannel的有三个类,分别是FileInputStream, ,FileOutputStream以及既能读又能写的RandomAccessFile

源码如下

public static void zipFileChannel() {    //开始时间    long beginTime = System.currentTimeMillis();    File zipFile = new File(ZIP_FILE);    try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));            WritableByteChannel writableByteChannel = Channels.newChannel(zipOut)) {        for (int i = 0; i 10; i++) {            try (FileChannel fileChannel = new FileInputStream(JPG_FILE).getChannel()) {                zipOut.putNextEntry(new ZipEntry(i + SUFFIX_FILE));                fileChannel.transferTo(0, FILE_SIZE, writableByteChannel);            }        }        printInfo(beginTime);    } catch (Exception e) {        e.printStackTrace();    }}

我们可以看到这里并没有使用ByteBuffer进行数据传输,甚至使用了transferTo的方法。这个方法是将两个通道进行直连。

This method is potentially much more efficient than a simple loop* that reads from this channel and writes to the target channel.  Many* operating systems can transfer bytes directly from the filesystem cache* to the target channel without actually copying them.

这是二进制上的描述文字,大概意思就是使用transferTo的效率比循环一个Channel读取出来然后重启写入另一个Channel好。操作系统能够直接传输字节从文件系统缓存到目标的Channel中,而不需要实际的copy阶段。

复制阶段就是从内核空间转到用户空间的一个过程

可以看到速度相比较使用范围已经有了一些的提高。

------ChannelfileSize:20Mconsum time:1416

内核空间和用户空间

那么为什么从内核空间转向用户空间阶段过程会慢呢??首先我们需要了解的是什么是内核空间和用户空间。在常用的操作系统中为了保护系统中的核心资源,于是将系统设计为四个区域,越往里权限限制,所以Ring0被称为内核空间,用来访问一些关键性的资源。Ring3被称为用户空间。


图片

用户态,内核态:线程位于内核空间称为内核态,线程处于用户空间属于用户态

那么我们如果此时应用程序(应用程序是都属于用户态的)需要访问核心资源怎么办呢?那就需要调用内核中所暴露的接口调用,称为系统调用。例如此时我们应用程序需要访问磁盘上的文件。此时应用程序就会调用系统调用的接口open方法,然后内核去访问磁盘中的文件,将文件内容返回给应用程序。


图片

直接罪行和非直接罪行

既然我们要读取一个磁盘的文件,要废这么大的周折。有没有什么简单的方法能够使我们的应用直接操作磁盘文件,不需要内核进行中转呢?有,那就是建立直接归档了。

  • 非直接竞争:非直接歧视就是我们上面所讲的内核态作为中间人,每次都需要内核在中间作为中转。


    图片

  • 直接分区:直接偏置不需要内核空间作为中转副本数据,或者直接在物理内存申请一块空间,这块空间映射到内核地址空间和用户地址空间,应用程序与磁盘之间数据的访问通过这块直接申请的物理内存进行交互。


    图片

既然直接缩短那么快,我们为什么不都用直接盯呢呢?实际上直接切断有以下的缺点。直接截断的缺点:

  1. 不安全
  2. 消耗更多,因为它不是在JVM中直接开辟空间。这部分内存的回收只能依赖于垃圾回收机制,垃圾什么时候回收不受我们控制。
  3. 数据写入物理内存缓冲区中,程序就丢失了对这些数据的管理,即什么时候这些数据被最终写入从磁盘只能由操作系统来决定,应用程序无法重新插入。

综上所述,所以我们使用transferTo方法就是直接开辟了一段直接短路。所以性能相对而言提高了很多

使用内存映射文件

NIO中新出的另一个特性就是内存映射文件,内存映射文件为什么速度快呢?其实原因和上面所讲的一样,也是在内存中开辟了一段直接分开。与数据直接作交互。

//Version 4 使用Map映射文件public static void zipFileMap() {    //开始时间    long beginTime = System.currentTimeMillis();    File zipFile = new File(ZIP_FILE);    try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));            WritableByteChannel writableByteChannel = Channels.newChannel(zipOut)) {        for (int i = 0; i 10; i++) {

            zipOut.putNextEntry(new ZipEntry(i + SUFFIX_FILE));

            //内存中的映射文件            MappedByteBuffer mappedByteBuffer = new RandomAccessFile(JPG_FILE_PATH, "r").getChannel()                    .map(FileChannel.MapMode.READ_ONLY, 0, FILE_SIZE);

            writableByteChannel.write(mappedByteBuffer);        }        printInfo(beginTime);    } catch (Exception e) {        e.printStackTrace();    }}

打印如下

---------MapfileSize:20Mconsum time:1305

可以看到速度和使用Channel的速度差不多的。

使用管道

Java NIO管道是2个线程之间的单向数据连接。Pipe有一个源通道和一个接收器通道。其中的源通道用于读取数据,接收器用于写入数据。如果没有数据偏移,读线程也会重叠至写入线程写入数据。直到通道关闭。

 Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes

图片

我想要的效果是这样的。

//Version 5 使用Pippublic static void zipFilePip() {

    long beginTime = System.currentTimeMillis();    try(WritableByteChannel out = Channels.newChannel(new FileOutputStream(ZIP_FILE))) {        Pipe pipe = Pipe.open();        //异步任务        CompletableFuture.runAsync(()->runTask(pipe));

        //获取读通道        ReadableByteChannel readableByteChannel = pipe.source();        ByteBuffer buffer = ByteBuffer.allocate(((int) FILE_SIZE)*10);        while (readableByteChannel.read(buffer)>= 0) {            buffer.flip();            out.write(buffer);            buffer.clear();        }    }catch (Exception e){        e.printStackTrace();    }    printInfo(beginTime);

}

//异步任务public static void runTask(Pipe pipe) {

    try(ZipOutputStream zos = new ZipOutputStream(Channels.newOutputStream(pipe.sink()));            WritableByteChannel out = Channels.newChannel(zos)) {        System.out.println("Begin");        for (int i = 0; i 10; i++) {            zos.putNextEntry(new ZipEntry(i+SUFFIX_FILE));

            FileChannel jpgChannel = new FileInputStream(new File(JPG_FILE_PATH)).getChannel();

            jpgChannel.transferTo(0, FILE_SIZE, out);

            jpgChannel.close();        }    }catch (Exception e){        e.printStackTrace();    }}

总结

  • 生活处处都需要学习,有时候只是一个简单的优化,可以让你深入学习到各种不同的知识。所以在学习中要不求甚解,完全要知道这个知识也要了解为什么要这么做。
  • 知行合一:学习完一个知识要努力应用一遍。这样才能记得牢靠。

原始地址

参考文章

  • https://www.jianshu.com/p/f90866dcbffc
  • https://juejin.im/post/5af942c6f265da0b7026050c
  • 趣谈Linux操作系统
  • JAVA NIO直接透视和非直接重叠

推荐阅读

• 如何让 Mybatis 自动生成代码,提升开发效率• 扎心了! 面试官: 你有自己的云服务器吗?• 看看人家那后端API接口写得,那叫一个优雅!• 是时候扔掉 Postman 了,试试 IntelliJ IDEA 自带的高能神器!

最近面试BATJ,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

fileinputstream读取文件_压缩 20M 文件从 30 秒到 1 秒的优化过程相关推荐

  1. fileinputstream resources 读取文件_压缩20M文件从30秒到1秒,包教包会

    作者:不学无数的程序员链接:https://www.jianshu.com/p/25b328753017 压缩20M文件从30秒到1秒的优化过程 有一个需求需要将前端传过来的10张照片,然后后端进行处 ...

  2. 压缩 20M 文件从 30 秒到 1 秒的优化过程!

    目录 第一次优化过程-从30秒到2秒 第二次优化过程-从2秒到1秒 使用Channel 使用内存映射文件 使用Pipe 总结 参考文章 有一个需求需要将前端传过来的10张照片,然后后端进行处理以后压缩 ...

  3. 压缩20M文件从30秒到1秒的优化过程

    有一个需求需要将前端传过来的10张照片,然后后端进行处理以后压缩成一个压缩包通过网络流传输出去.之前没有接触过用Java压缩文件的,所以就直接上网找了一个例子改了一下用了,改完以后也能使用,但是随着前 ...

  4. Java 压缩20M文件从30秒到1秒的优化过程,真不相信?

    (源码下载方式在文末) 有一个需求需要将前端传过来的10张照片,然后后端进行处理以后压缩成一个压缩包通过网络流传输出去.之前没有接触过用Java压缩文件的,所以就直接上网找了一个例子改了一下用了,改完 ...

  5. Java压缩20M文件非常厉害

    有一个需求需要将前端传过来的10张照片,然后后端进行处理以后压缩成一个压缩包通过网络流传输出去.之前没有接触过用Java压缩文件的,所以就直接上网找了一个例子改了一下用了,改完以后也能使用,但是随着前 ...

  6. pydicom读取头文件_.dcm格式文件软件读取及python处理详解

    要处理一些.dcm格式的焊接缺陷图像,需要读取和显示.dcm格式的图像.通过搜集资料收集到一些医学影像,并通过pydicom模块查看.dcm格式文件. 若要查看dcm格式文件,可下echo viewe ...

  7. python输入文件名读取文件_[Python] python3 文件操作:从键盘输入、打开关闭文件、读取写入文件、重命名与删除文件等...

    1.从键盘输入 Python 2有两个内置的函数用于从标准输入读取数据,默认情况下来自键盘.这两个函数分别是:input()和raw_input(). Python 3中,不建议使用raw_input ...

  8. C 怎么读取Cpp文件_从PCD文件写入和读取点云数据

    (1)学习向PCD文件写入点云数据 建立工程文件ch2,然后新建write_pcd.cpp CMakeLists.txt两个文件 write_pcd.cpp : #include <iostre ...

  9. arcgis怎么压缩tif文件_微信传文件有大小限制怎么办?教你3秒把100MPPT压缩成10M...

    现在用微信来办公的人越来越多了,不过大家都知道,微信传文件的时候有点小麻烦,文件的大小有限制,电脑版最大只能传25M的文件,一些比较大的文件,像PPT,一般都几十M,大的几百M都有,无法通过微信传输, ...

最新文章

  1. python零基础怎么学-编程零基础应当如何开始学习 Python?
  2. (75)内核APC执行过程,分析 KiDeliverApc 函数
  3. Drools 在告警关联分析中的应用
  4. Android:在安卓中使用TFLite模型
  5. 把室友的STM32换成了GD32,会被打吗?
  6. jdk卸载不干净怎么办_雨刮器“刮不干净”怎么办?老司机:用这招,分分钟解决!...
  7. 辽源a货翡翠,张掖a货翡翠
  8. c语言用户登录成功才能,c语言用户登录.docx
  9. cloudera-scm-agent 已死,但 pid 文件存在
  10. 计算机应用助手工程师,通信工程师备考助手
  11. 前端机器人流程设计的最佳实践:输入输出文件结构和逻辑框架
  12. 最近在GitHub 超人气的国人项目和公众号
  13. Topic ods_base_db not present in metadata after 60000 ms.
  14. SSM全注解开发的网上商城系统
  15. cosolog打印带样式的文字及图片
  16. 网格交易模型 股票网格交易 期货网格交易 策略开发
  17. DoNet:浅淡对delegate的理解
  18. MATLAB如何生成scr文件,基于SCR脚本文件的MatlabAutoCAD结合使用
  19. 一篇读懂|Linux系统平均负载
  20. 如何反编译apk文件并解析.class文件查看Java源代码

热门文章

  1. Greenplum分区
  2. 【SQL】SQL语法树
  3. 01-浏览器同源政策 以及 什么是跨域?怎么解决跨域问题?
  4. struts2+hibernate+spring配置详解
  5. linux套接字端口,多端口监听套接字linux(multiport listening socket linux)
  6. java中static类的作用是什么意思_java中static关键字是什么意思
  7. Java多线程学习二十八:原子类和 volatile 有什么异同?
  8. php设置session 生命周期,php会话(session)生命周期概念介绍及设置更改和回收
  9. 用这款免费工具,即可解决 90% 的报表设计难题
  10. shell基础07 函数