一文探索Java文件读写更高效方式

目录
  • 背景
  • 场景分析
    • 场景1:小文件单文件压缩
      • 方式1:网上流传(流传在坊间的神话,其实是带刺的玫瑰)
      • 方式2:使用缓冲区
      • 方式3:使用通道
      • 方式4:使用mmp
    • 场景2:大文件单文件压缩
    • 场景3:大文件多文件压缩
  • 分析结论
  • 背后机密
    • 1、网上流传方法
    • 2、使用缓冲区
    • 3、使用通道
    • 4、使用mmp

背景

最近在探秘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文件读写内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一文秒懂通过JavaCSV类库读写CSV文件的技巧

    一.背景 小哈公司最近准备开发一套新的平台,具体什么平台,因为涉密,这里就不透露了.平台在最终的的技术选型中,其中主要依赖的技术栈是 Apache Flink, 一款 Apache 基金会开源的流处理框架,平台的核心业务都会交给 Flink 去处理,其中包括离线批量任务计算,以及实时任务计算. PS: 后面小哈也会分享一些 Flink 相关的文章,正在考虑要不要立个 Flag, 出一套 Flink 的入门教程系列文章,主要怕自己太懒了,泼出去的水,收不回来,那就尴尬了~

  • java 读写 ini 配置文件的示例代码

    下面通过代码先看下java 读写 ini 配置文件,代码如下所示: package org.fh.util; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.net.URLDecoder; import java.util.regex.

  • 基于Java实现Avro文件读写功能

    目录 模式(schema) 与其他系统的比较 Java客户端实现 定义一个schema 使用Java代码生成插件生成的User类进行序列化和反序列化 在不生成User类的情况下直接进行序列化和反序列化操作 总结 Apache Avro是一个数据序列化系统.具有如下基本特性: 丰富的数据结构.一种紧凑.快速的二进制数据格式.一个容器文件,用于存储持久数据.远程过程调用 (RPC).与动态语言的简单集成. 代码生成不需要读取或写入数据文件,也不需要使用或实现 RPC 协议. 代码生成作为一种可选的优

  • Java中文件的读写方法之IO流详解

    目录 1.File类 1.1File类概述和构造方法 1.2File类创建功能 1.3File类判断和获取功能 1.4File类删除功能 2.递归 2.1递归 2.2递归求阶乘 2.3递归遍历目录 3.IO流 3.1 IO流概述和分类 3.2字节流写数据 3.3字节流写数据的三种方式 3.4字节流写数据的两个小问题 3.5字节流写数据加异常处理 3.6字节流读数据(一次读一个字节数据) 3.7字节流复制文本文件 3.8字节流读数据(一次读一个字节数组数据) 3.9字节流复制图片 总结 1.Fil

  • Java详细讲解文件的读写操作方法

    目录 java的IO 字节流 InputStream的常用方法 OutputStream的常用方法 字节流读写文件 如何将数据写入到文件中 java的IO Java程序允许通过流的方式与输入输出设备进行数据传输.Java中的流都在java.io包中,称为IO(输入输出)流.IO流按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同,又可以分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据,在IO包中,字节流的输入输出分别用java.InputStream和java.io

  • Java 如何利用缓冲流读写文件

    利用缓冲流读写文件 从控制台读取数据写入文件 读取文件输出到控制台 public class BookTest { public static void main(String[] args) { //从控制台输入信息并写入文件中 BufferedReader ir=new BufferedReader(new InputStreamReader(System.in)); //包装成字符输入缓冲流 BufferedWriter bw=null; try { bw=new BufferedWrit

  • java文件读写操作实例详解

    目录 File类 File类的构造方法 创建功能 判断 获取 删除 IO流 字节流写数据 小问题 总结 File类 它是文件和目录路径名的抽象表示. 文件和目录是可以通过File封装成对象的. 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已.它可以是存在的,也可以是不存在的.将来是要通过具体的操作把这个路径的内容转换为具体存在的. File类的构造方法 File(String pathname) //通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 Fi

  • 浅谈Java中File文件的创建以及读写

    1.创建一个文件 @Test public void test6() throws IOException { File file1 = new File("C:\\IDEA\\h1.txt"); if(!file1.exists()){//文件不存在 file1.createNewFile(); System.out.println("创建成功"); }else{//文件存在 file1.delete(); System.out.println("删除成

  • 一文探索Java文件读写更高效方式

    目录 背景 场景分析 场景1:小文件单文件压缩 方式1:网上流传(流传在坊间的神话,其实是带刺的玫瑰) 方式2:使用缓冲区 方式3:使用通道 方式4:使用mmp 场景2:大文件单文件压缩 场景3:大文件多文件压缩 分析结论 背后机密 1.网上流传方法 2.使用缓冲区 3.使用通道 4.使用mmp 背景 最近在探秘kafka为什么如此快?其背后的秘诀又是什么? 怀着好奇之心,开始像剥洋葱 一样逐层内嵌.一步步揭晓kafka能够吊打mq的真因.了解之后不得不说kafka:yyds. 了解到顺序存盘的

  • Java文件读写IO/NIO及性能比较详细代码及总结

    干Java这么久,一直在做WEB相关的项目,一些基础类差不多都已经忘记.经常想得捡起,但总是因为一些原因,不能如愿. 其实不是没有时间,只是有些时候疲于总结,今得空,下定决心将丢掉的都给捡起来. 文件读写是一个在项目中经常遇到的工作,有些时候是因为维护,有些时候是新功能开发.我们的任务总是很重,工作节奏很快,快到我们不能停下脚步去总结. 文件读写有以下几种常用的方法 1.字节读写(InputStream/OutputStream) 2.字符读取(FileReader/FileWriter) 3.

  • java文件读写工具类分享

    本文实例为大家分享了java文件读写工具类的具体代码,供大家参考,具体内容如下 import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter;

  • 基于MFC实现单个文档的文件读写

    目录 写文件 1.添加相应菜单以及ID 2.完善相应的事件响应函数 3.进行调试测试 4.根据测试结果进行相应的修改 读文件 1.为读文件添加事件响应函数 2.完善事件响应函数的代码 3.调试测试 写文件 预期效果: 当点击确定时弹出“是否替换”提示框,指的是是否替换原来文本里的内容. 1.添加相应菜单以及ID 在菜单栏中添加相应菜单及其ID,并在view.cpp中添加相应的事件处理函数 2.完善相应的事件响应函数 这是我们写文件函数代码 void Cdraw3View::OnWriteFile

  • java 文件流的处理方式 文件打包成zip

    目录 java 文件流的处理 文件打包成zip 1.下载文件到本地 2.java后端下载 3.文件打包成zip 后台多文件打包成zip返回流 前台提供按钮一键下载 java 文件流的处理 文件打包成zip 1.下载文件到本地 public void download(HttpServletResponse response){ String filePath ="";//文件路径 String fileName ="";//文件名称 // 读到流中 InputStr

  • JAVA文件读写例题实现过程解析

    练习 有这样的一个words数组,数组中每个字符串的格式为"词性:单词" String[] words = {"verb:eat","verb:drink","verb:sleep","verb:play","noun:rice","noun:meat","noun:hand","noun:hair"}; 根据单词性质动词ver

  • JAVA文件读写操作详解

    目录 一.读文件BufferedInputStream 二.写文件BufferedOutputStream 三.实际应用场景 总结 一.读文件BufferedInputStream BufferedInputStream必须传入一个InputStream(一般是FileInputStream) 常用方法: 从该输入流中读取一个字节 public int read(); 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中. public int read(byte[] b,in

  • java文件的重命名与移动操作实例代码

    文件的重命名与移动操作 有时候为了对文件进行统一访问与管理,需要把文件进行重命名,并移动到新的文件夹,如何实现呢? 一枚简单的java小程序即可实现: part_1:需求:我需要把<(E:\BaiduYun\传智播客_张孝祥_Java多线程与并发库高级应用视频教程下载)>文件夹下的所有子文件夹下的视频文件重命名,并移动到新的位置<(E:\BaiduYun\张孝祥_Java多线程与并发库)>; part_2:目录结构如下: E:\BaiduYun E:\BaiduYun\传智播客_张

  • Python使用os模块实现更高效地读写文件

    目录 使用 os.open 打开文件 使用 os.read 读取文件 使用 os.write 写入文件 使用 os.open 打开文件 无论是读文件还是写文件,都要先打开文件.说到打开文件,估计首先想到的就是内置函数 open(即 io.open),那么它和 os.open 有什么关系呢? 内置函数 open 实际上是对 os.open 的封装,在 os.open 基础上增加了相关访问方法.因此为了操作方便,应该调用内置函数 open 进行文件操作,但如果对效率要求较高的话,则可以考虑使用 os

随机推荐