<small id='roDxt'></small> <noframes id='Ab0rTd6'>

  • <tfoot id='AQUt4T7wsL'></tfoot>

      <legend id='YHksLTB2'><style id='sJQ9'><dir id='3FsZWAbot'><q id='paloQc'></q></dir></style></legend>
      <i id='7BWeD'><tr id='ur70'><dt id='MvihpQtBX'><q id='owekuWiZSj'><span id='q9cfw2'><b id='ahAFQcE'><form id='zx6L3lDw2'><ins id='w1ioQ'></ins><ul id='nluDviT'></ul><sub id='LvY50sTy'></sub></form><legend id='rqBtEJ'></legend><bdo id='wPr2Vy'><pre id='vxPrG'><center id='74sume6'></center></pre></bdo></b><th id='cyH9Rt'></th></span></q></dt></tr></i><div id='g5f7p'><tfoot id='tQFI9L'></tfoot><dl id='hGFU1D'><fieldset id='FPfp'></fieldset></dl></div>

          <bdo id='ENiXoT'></bdo><ul id='2gK9chaC'></ul>

          1. <li id='JWI1M'></li>
            登陆

            紧缩20M文件从30秒到1秒的优化进程

            admin 2019-09-06 151人围观 ,发现0个评论

            有一个需求需求将前端传过来的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:紧缩20M文件从30秒到1秒的优化进程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 -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内部封装了一个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

            能够看到比较较于第一次运用FileInputStream功率现已提升了许多了

            第2次优化进程-从2秒到1秒

            运用缓冲区buffer的话现已是满意了我的需求了,可是秉着学以致用的主意,就想着用NIO中常识进行优化一下。

            运用Channel

            为什么要用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阶段。

            > copy阶段便是从内核空间转到用户空间的一个进程

            能够看到速度比较较运用缓冲区现已有了一些的进步。

            ------Channel
            fileSize:20M
            consum time:1416

            内核空间和用户空间

            那么为什么从内核空间转向用户空间这段进程会慢呢?首要咱们需了解的是什么是内核空间和用户空间。在常用的操作体系中为了维护体系中的中心资源,所以将体系规划为四个区域,越往里权限越大,所以Ring0被称之为内核空间,用来拜访一些关键性的资源。Ring3被称之为用户空间。

            > 用户态、内核态:线程处于内核空间称之为内核态,线程处于用户空间归于用户态

            那么咱们假如此刻运用程序(运用程序是都归于用户态的)需求拜访中心资源怎么办呢?那就需求调用内核中所暴露出的接口用以调用,称之为体系调用。例如此刻咱们运用程序需求拜访磁盘上的文件。此刻运用程序就会调用体系调用的接口open办法,然后内核去拜访磁盘中的文件,将文件内容回来给运用程序。大致的流程如下

            直接缓冲区和非直接缓冲区

            已然咱们要读取一个磁盘的文件,要废这么大的曲折。有没有什么简略的办法能够使咱们的运用直接操作磁盘文件,不需求内核进行中转呢?有,那便是树立直接缓冲区了。

            • 非直接缓冲区:非直接缓冲区便是咱们上面所讲内核态作为中心人,每次都需求内核在中心作为中转。

            • 直接缓冲区:直接缓冲区不需求内核空间作为中转copy数据,而是直接在物理内存请求一块空间,这块空间映射到内核地址空间和用户地址空间,运用程序与磁盘之间数据的存取经过这块直接请求的物理内存进行交互。

            • 已然直接缓冲区那么快,咱们为什么不都用直接缓冲区呢?其实直接缓冲区有以下的缺陷。直接缓冲区的缺陷:
            1. 不安全
            2. 耗费更多,由于它不是在JVM中直接拓荒空间。这部分内存的收回只能依赖于废物收回机制,废物什么时分收回不受咱们操控。
            3. 数据写入物理内存缓冲区中,程序就丧失了对这些数据的办理,即什么时分这些数据被终究写入从磁盘只能由操作体系来决议,运用程序无法再干与。

            > 综上所述,所以咱们运用transferTo办法便是直接拓荒了一段直接缓冲区。所以功能比较而言进步了许多

            运用内存映射文件

            NIO中新出的另一个特性便是内存映射文件,内存映射文件为什么速度快呢?其实原因和上面所讲的相同,也是在内存中拓荒了一段直接缓冲区。与数据直紧缩20M文件从30秒到1秒的优化进程接作交互。源码如下

            //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").ge紧缩20M文件从30秒到1秒的优化进程tChannel()

            .map(FileChannel.MapMode.READ_ONLY, 0, FILE_SIZE);

            writableByteChannel.write(mappedByteBuffer);

            }

            printInfo(beginTime);

            } catch (Exception e) {

            e.printStackTrace();

            }

            }

            打印如下

            ---------Map
            fileSize:20M
            consum time:1305

            能够看到速度和运用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

            我想要的作用是这样的。源码如下

            //Version 5 运用Pip
            public static紧缩20M文件从30秒到1秒的优化进程 void zipFilePip() {
            long beginTime = System.currentTimeMillis();
            try(WritableByteChannel out = Channels.newChannel(new FileOutputStream(ZIP_FILE))) {
            Pipe pipe = Pipe.open();
            //异步使命
            CompletableFutu紧缩20M文件从30秒到1秒的优化进程re.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://github.com/modouxiansheng/Doraemon

            参阅文章

            • https://www.jianshu.com/p/f90866dcbffc
            • https://juejin.im/post/5af942c6f265da0b7026050c
            • 趣谈Linux操作体系:https://time.geekbang.org/column/article/90109
            • JAVA NIO 直接缓冲区和非直接缓冲区:https://my.oschina.net/happyBKs/blog/1592329

            原文链接:https://my.oschina.net/u/4030990/blog/3093411

            请关注微信公众号
            微信二维码
            不容错过
            Powered By Z-BlogPHP