共计 2516 个字符,预计需要花费 7 分钟才能阅读完成。
作者:不学有数的程序员,原文链接:https://urlify.cn/7NRrea
压缩 20M 文件从 30 秒到 1 秒的优化过程
有一个需要须要将前端传过来的 10 张照片,而后后端进行解决当前压缩成一个压缩包通过网络流传输入去。之前没有接触过用 Java 压缩文件的,所以就间接上网找了一个例子改了一下用了,改完当前也能应用,然而随着前端所传图片的大小越来越大的时候,消耗的工夫也在急剧减少,最初测了一下压缩 20M 的文件居然须要 30 秒的工夫。压缩文件的代码如下。
这里找了一张 2M 大小的图片,并且循环十次进行测试。打印的后果如下,工夫大略是 30 秒。
第一次优化过程 - 从 30 秒到 2 秒
进行优化首先想到的是利用缓冲区 BufferInputStream。在 FileInputStream 中 read() 办法每次只读取一个字节。源码中也有阐明。
这是一个调用本地办法与原生操作系统进行交互,从磁盘中读取数据。每读取一个字节的数据就调用一次本地办法与操作系统交互,是十分耗时的。例如咱们当初有 30000 个字节的数据,如果应用 FileInputStream 那么就须要调用 30000 次的本地办法来获取这些数据,而如果应用缓冲区的话(这里假如初始的缓冲区大小足够放下 30000 字节的数据)那么只须要调用一次就行。因为缓冲区在第一次调用 read() 办法的时候会间接从磁盘中将数据间接读取到内存中。随后再一个字节一个字节的缓缓返回。
BufferedInputStream 外部封装了一个 byte 数组用于存放数据,默认大小是 8192
优化过后的代码如下
输入
能够看到相比拟于第一次应用 FileInputStream 效率曾经晋升了许多了
第二次优化过程 - 从 2 秒到 1 秒
应用缓冲区 buffer 的话曾经是满足了我的需要了,然而秉着学以致用的想法,就想着用 NIO 中常识进行优化一下。
应用 Channel
为什么要用 Channel 呢?因为在 NIO 中新出了 Channel 和 ByteBuffer。正是因为它们的构造更加合乎操作系统执行 I / O 的形式,所以其速度相比拟于传统 IO 而言速度有了显著的进步。Channel 就像一个蕴含着煤矿的矿藏,而 ByteBuffer 则是派送到矿藏的卡车。也就是说咱们与数据的交互都是与 ByteBuffer 的交互。
在 NIO 中可能产生 FileChannel 的有三个类。别离是 FileInputStream、FileOutputStream、以及既能读又能写的 RandomAccessFile。
源码如下
咱们能够看到这里并没有应用 ByteBuffer 进行数据传输,而是应用了 transferTo 的办法。这个办法是将两个通道进行直连。
这是源码上的形容文字,大略意思就是应用 transferTo 的效率比循环一个 Channel 读取进去而后再循环写入另一个 Channel 好。操作系统可能间接传输字节从文件系统缓存到指标的 Channel 中,而不须要理论的 copy 阶段。
copy 阶段就是从内核空间转到用户空间的一个过程
能够看到速度相比拟应用缓冲区曾经有了一些的进步。
内核空间和用户空间
那么为什么从内核空间转向用户空间这段过程会慢呢?首先咱们需理解的是什么是内核空间和用户空间。在罕用的操作系统中为了爱护零碎中的外围资源,于是将零碎设计为四个区域,越往里权限越大,所以 Ring0 被称之为内核空间,用来拜访一些关键性的资源。Ring3 被称之为用户空间。
用户态、内核态:线程处于内核空间称之为内核态,线程处于用户空间属于用户态
那么咱们如果此时应用程序(应用程序是都属于用户态的)须要拜访外围资源怎么办呢?那就须要调用内核中所暴露出的接口用以调用,称之为零碎调用。例如此时咱们应用程序须要拜访磁盘上的文件。此时应用程序就会调用零碎调用的接口 open 办法,而后内核去拜访磁盘中的文件,将文件内容返回给应用程序。大抵的流程如下
间接缓冲区和非间接缓冲区
既然咱们要读取一个磁盘的文件,要废这么大的周折。有没有什么简略的办法可能使咱们的利用间接操作磁盘文件,不须要内核进行直达呢?有,那就是建设间接缓冲区了。
非间接缓冲区
:非间接缓冲区就是咱们下面所讲内核态作为中间人,每次都须要内核在两头作为直达。
间接缓冲区
:间接缓冲区不须要内核空间作为直达 copy 数据,而是间接在物理内存申请一块空间,这块空间映射到内核地址空间和用户地址空间,应用程序与磁盘之间数据的存取通过这块间接申请的物理内存进行交互。
既然间接缓冲区那么快,咱们为什么不都用间接缓冲区呢?其实间接缓冲区有以下的毛病。间接缓冲区的毛病:
- 不平安
- 耗费更多,因为它不是在 JVM 中间接开拓空间。这部分内存的回收只能依赖于垃圾回收机制,垃圾什么时候回收不受咱们管制。
- 数据写入物理内存缓冲区中,程序就丢失了对这些数据的治理,即什么时候这些数据被最终写入从磁盘只能由操作系统来决定,应用程序无奈再干预。
综上所述,所以咱们应用 transferTo 办法就是间接开拓了一段间接缓冲区。所以性能相比而言进步了许多
应用内存映射文件
NIO 中新出的另一个个性就是内存映射文件,内存映射文件为什么速度快呢?其实起因和下面所讲的一样,也是在内存中开拓了一段间接缓冲区。与数据间接作交互。源码如下
打印如下
能够看到速度和应用 Channel 的速度差不多的。
应用 Pipe
Java NIO 管道是 2 个线程之间的单向数据连贯。Pipe 有一个 source 通道和一个 sink 通道。其中 source 通道用于读取数据,sink 通道用于写入数据。能够看到源码中的介绍,大略意思就是写入线程会阻塞至有读线程从通道中读取数据。如果没有数据可读,读线程也会阻塞至写线程写入数据。直至通道敞开。
Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes
我想要的成果是这样的。源码如下:
源码地址 https://github.com/modouxiansheng/Doraemon
总结
生存处处都须要学习,有时候只是一个简略的优化,能够让你深刻学习到各种不同的常识。所以在学习中要不求甚解,不仅要晓得这个常识也要理解为什么要这么做。