背景

最近在探秘kafka为什么如此快?其背后的秘诀又是什么?

怀着好奇之心,开始像剥洋葱 一样逐层内嵌。一步步揭晓kafka能够吊打mq的真因。了解之后不得不说kafka:yyds。

了解到顺序存盘的运用

探测到稀疏索引的引进

知晓其零拷贝技术的威力

嗅觉到mmp(内存映射文件)的神来之笔

......

mmp如此神奇,那么运用于文件压缩,是否同样可以实现飞速压缩呢?

又怀着好奇之心,决定用实际行动证明这个结论(否则我们的知识只能纸上谈兵)

编码是我们的本能功能,好奇之心是我们永远的利器。不能丢

曾几何时,有位BA告诉我他的经历:DEV转为BA后,代码就生疏了,后来他强迫自己每个迭代都领一个小需求鞭策自己。

曾几何时,有位前辈告诉我:即使你以后成长为架构师甚至更高职位,也不能丢失编码这件神器。否则你会发现会很尴尬——会被人称为“需求翻译机”

......

这不是心灵鸡汤,这是来自灵魂的谏言,我深刻了解到:编码真的是学到老活到老的工作。

看到很多优秀的同事离职远去,通过交流感触更加深厚

所以,大家一定记得:学会一个知识要努力应用一遍。这样才能记得牢固;在学习中要不求甚解,完全知道这个知识也要知道为什么这么做

......

场景分析

场景1:小文件单文件压缩

  • 1、原始文件介绍:63.7M、 csv文件 、单个文件
  • 2、对比技术介绍:网上流传、使用缓冲区、使用管道、使用mmp
  • 3、对比结果展示:

方式1:网上流传(流传在坊间的神话,其实是带刺的玫瑰)

小王刚入职不久,有一天突然接到需求,要压缩文件,之前没写过,怎么办?这个时候会在网上搜到这个方法

执行结果(效率很吓人)

zipMethod=withoutBuffer

costTime=327000ms

代码如下:

public void zipFileWithoutBuffer(String outFile){
    long beginTime = System.currentTimeMillis();
    File zipFile = new File(outFile);
    File inputFile = new File(INPUT_FILE);
    try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
        try (InputStream inputStream = new FileInputStream(inputFile)){
            zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
            int temp;
            while ((temp = inputStream.read()) != -1){
                zipOutputStream.write(temp);
            }
        }
        printResult(beginTime,"withoutBuffer");
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("error"   e.getMessage());
    } 
}

方式2:使用缓冲区

小王很开心,提交代码,翻转了需求状态,可验收。

小花是团队资深技术达人,走查代码发现一脸懵逼:网上搜的?这个会很慢,你再研究研究

小王又换了一种思路,借助缓冲区BufferedOutputStream

执行结果(快了很多)

zipMethod=withBuffer

costTime=5170ms

代码如下:

public void zipFileWithBuffer(String outFile){
    long beginTime = System.currentTimeMillis();
    File zipFile = new File(outFile);
    File inputFile = new File(INPUT_FILE);
    try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(zipOutputStream)) {
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(inputFile))){
            zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
            int temp;
            while ((temp = bufferedInputStream.read()) != -1){
                bufferedOutputStream.write(temp);
            }
        }
        printResult(beginTime,"withBuffer");
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("error"   e.getMessage());
    } 
}

方式3:使用通道

小王怀着忐忑的心情,又一次召集大家走查代码。

小花:速度要求没那么高,这样做已经差不多了,代码可以提交了

其实最近研究kafka,接触过nio,知晓:nio有种技术叫通道:Channel

执行结果(好快)

zipMethod=withChannel

costTime=1642ms

代码如下:

public void zipFileWithChannel(String outFile){
    long beginTime = System.currentTimeMillis();
    File zipFile = new File(outFile);
    File inputFile = new File(INPUT_FILE);
    try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
        WritableByteChannel writableByteChannel = Channels.newChannel(zipOutputStream)) {
        try (FileChannel fileChannel = new FileInputStream(inputFile).getChannel()){
            zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
            fileChannel.transferTo(0,inputFile.length(),writableByteChannel);
        }
        printResult(beginTime,"withChannel");
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("error"   e.getMessage());
    } 
}

方式4:使用mmp

研究kafka过程中,不止知晓nio有种技术叫通道:Channel,还有种技术叫mmp

执行结果(好快)

zipMethod=withMmp

costTime=1554ms

代码如下:

public void zipFileWithMmp(String outFile){
    long beginTime = System.currentTimeMillis();
    File zipFile = new File(outFile);
    File inputFile = new File(INPUT_FILE);
    try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
        WritableByteChannel writableByteChannel = Channels.newChannel(zipOutputStream)) {
        zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
        MappedByteBuffer mappedByteBuffer = new RandomAccessFile(INPUT_FILE,"r").getChannel()
                .map(FileChannel.MapMode.READ_ONLY,0,inputFile.length());
        writableByteChannel.write(mappedByteBuffer);
        printResult(beginTime,"withMmp");
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("error"   e.getMessage());
    } 
}

场景2:大文件单文件压缩

  • 1、原始文件介绍:585M、 csv文件 、单个文件
  • 2、对比技术介绍:使用缓冲区、使用管道、使用mmp
  • 3、对比结果展示:
使用缓冲区 使用通道 使用mmp
costTime=46034ms costTime=11885ms costTime=10810ms

场景3:大文件多文件压缩

  • 1、原始文件介绍:585M、 csv文件 、5个文件
  • 2、对比技术介绍:使用缓冲区、使用管道、使用mmp
  • 3、对比结果展示:
使用缓冲区 使用通道 使用mmp
costTime=173122ms costTime=53982ms costTime=50543ms

分析结论

1、对比见下表

压缩场景 网上流传 使用缓冲区 使用通道 使用mmp
场景1:小文件单文件压缩(60M) 327000ms 5170ms 1642ms 1554ms
场景2:大文件单文件压缩(585M) -- 46034ms 11885ms 10810ms
场景3:大文件多文件压缩(5个585M) -- 173122ms 53982ms 50543ms
场景4:100K文件单文件压缩 -- 28ms 26ms 24ms
场景5:5K文件单文件压缩   18ms 20ms 23ms
场景5:1K文件单文件压缩   15ms 21ms 24ms

结论:

  • 1)网上流传的方法不可取,效率最差
  • 2)使用缓冲区虽然性能还凑合,但和两种nio技术(通道和mmp)相比,还是差了很多,尤其是在中型文件(500M左右)的单文件压缩和多文件压缩
  • 中,对比更加明显
  • 3)通道技术和mmp技术对比相差不大,小型文件基本没影响,大型文件差距也在几秒之间
  • 4)文件大于10K时,推荐使用通道技术或者mmp技术进行文件压缩
  • 5)文件小于10K时,推荐使用缓冲区技术(比两种nio技术表现了更好的性能)
  • 6)如果有些团队在使用api,可以看看其源码是否使用了nio技术。如果不是,建议修改为文中方式

另外,操作文件操作时,都可以尝试使用nio技术,测试下其效率,理论上应该都是很可观的

背后机密

1、网上流传方法

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 int read() throws IOException {
    return read0();}private native int read0() throws IOException;

这是调用本地方法与原生操作系统进行交互,从磁盘中读取数据。每读取一个字节数据就调用一次这个方法(一次交互很耗时)。

这个方法还是每次读取一个字节,假如文件很大,这个开销是巨大的

2、使用缓冲区

BufferedInputSream read方法如下:

/**
 * See
 * the general contract of the <code>read</code>
 * method of <code>InputStream</code>.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             stream is reached.
 * @exception  IOException  if this input stream has been closed by
 *                          invoking its {@link #close()} method,
 *                          or an I/O error occurs.
 * @see        java.io.FilterInputStream#in
 */public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos  ] & 0xff;}

这样虽然也是一次读一个字节,但不是每次都从底层读取数据,而是一次调用底层系统读取了最多buf.length个字节到buf数组中,然后从 buf中一次读一个字节,减少了频繁调用底层接口的开销。

3、使用通道

在复制大文件时,FileChannel复制文件的速度比BufferedInputStream/BufferedOutputStream复制文件的速度快了近三分之一,体现出FileChannel的速度优势。NIO的Channel的结构更加符合操作系统执行I/O的方式,所以其速度相比较于传统的IO而言速度有了显著的提高。

操作系统能够直接传输字节从文件系统缓存到目标的Channel中,而不需要实际的copy阶段(copy: 从内核空间转到用户空间的一个过程)

4、使用mmp

内存映射文件,是把位于硬盘中的文件看做是程序地址空间中一块区域对应的物理存储器,文件的数据就是这块区域内存中对应的数据,读写文件中的数据,直接对这块区域的地址操作,就可以,减少了内存复制的环节。所以说,内存映射文件比起文件I/O操作,效率要高,而且文件越大,体现出来的差距越大。

到此这篇关于一文探索Java文件读写更高效方式的文章就介绍到这了,更多相关 Java文件读写内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

一文探索Java文件读写更高效方式的更多相关文章

  1. PHP实现文件安全下载

    例如你希望客户要填完一份表格,才可以下载某一文件,你第一个想法一定是用"Redirect"的方法,先检查表格是否已经填写完毕和完整,然后就将网址指到该文件,这样客户才能下载,但如果你想做一个关于"网上购物"的电子商务网站,考虑安全问题,你不想用户直接复制网址下载该文件,笔者建议你使用PHP直接读取该实际文件然后下载的方法去做。feof){echofread;}fclose;}这样就可以用PHP直接输出文件了。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  4. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  5. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  6. Java实现世界上最快的排序算法Timsort的示例代码

    Timsort 是一个混合、稳定的排序算法,简单来说就是归并排序和二分插入排序算法的混合体,号称世界上最好的排序算法。本文将详解Timsort算法是定义与实现,需要的可以参考一下

  7. Node的文件系统你了解多少

    这篇文章主要为大家详细介绍了Node的文件系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  8. 详解Python如何实现Excel数据读取和写入

    这篇文章主要为大家详细介绍了python如何实现对EXCEL数据进行读取和写入,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Java日期工具类的封装详解

    在日常的开发中,我们难免会对日期格式化,对日期进行计算,对日期进行校验,为了避免重复写这些琐碎的逻辑,我这里封装了一个日期工具类,方便以后使用,直接复制代码到项目中即可使用,需要的可以参考一下

  10. Java设计模式之模板方法模式Template Method Pattern详解

    在我们实际开发中,如果一个方法极其复杂时,如果我们将所有的逻辑写在一个方法中,那维护起来就很困难,要替换某些步骤时都要重新写,这样代码的扩展性就很差,当遇到这种情况就要考虑今天的主角——模板方法模式

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部