文件IO中,常用的方法如下方代码中的readMethod1~8方法所示。

测试了2.5M读100次、100M读3次、250M读1次三种情况,耗时(单位:毫秒)如下:

2.5M读100次 2.5M读100次 100M读3次 100M读3次 250M读1次 250M读1次
普通 HDFS 普通 HDFS 普通 HDFS
method1,一次性全部读取 635 1604 976 965 1270 482
method2,ByteArrayOutputStream+byte[] 616 5759 669 5135 843 4375
method3,InputStreamReader+char[8192]+StringWriter 1236 5097 1454 4370 1167 3976
method4,InputStreamReader+BufferedReader(char[8192]) 1565 4556 1986 4763 1608 3230
method5,bufferedReader+stream 1414 4167 62546 140485 - -
method6,bufferedReader+stream+parallel 1941 4526 OOM OOM OOM OOM
method7,Deque<byte[8196]> 628 5331 761 4456 669 3321
method8,ByteBuffer(2048)+LineBuffer 1910 5325 2310 4426 2300 3575

个人思考:

1、普通文件系统,使用char[]作为中间缓冲(method3~6),速度都比较慢,因为java的string底层是byte[],先转成char[],又转回byte[],会消耗多余的时间。

2、使用method6使用parallel并不能提升性能,因为底层InputStreamReader是加锁的,IO是不能并行的。

3、HDFS不会用,使用最朴素的连接方式,肯定是那里有问题,才会导致IO速度这么慢。但是好像一次性全部读取HDFS的速度,会随着文件的增大而相对更快。

4、最后method7是google guava库中的一种读取全部字符串的方法,脑洞大开,性能都还不错。

5、method3、4、5、6、8都是一行一行读取的模式,适用于需要对每一行进行后续处理的情况。

6、谨慎对读取全部字符串这种批作业使用流处理方式,速度很慢,parallel的甚至直接OOM。

7、总结下来,如果是读取文件中全部字符串,method2 和 method7都是比较不错的方式;如果是需要一行一行处理,则可能还是method4的BufferedReader性能更好。

package net.yury;

import com.google.common.io.ByteStreams;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.file.tfile.ByteArray; import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; public class Test1 {
public static FileSystem fileSystem;
static {
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://myubuntu1:8020");
try {
fileSystem = FileSystem.get(configuration);
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws Exception {
System.out.println("测试普通文件系统:");
testReadMethod(new InputStreamBuilder("NORMAL", "C:/Users/yury/Desktop/size100M.txt"), 3);
System.out.println("=====================================");
System.out.println("测试HDFS文件系统:");
testReadMethod(new InputStreamBuilder("HDFS", "/test1/size100M.txt"), 3);
} public static class InputStreamBuilder {
private String type;
private String fileName;
public InputStreamBuilder(String type, String fileName) {
this.type = type;
this.fileName = fileName;
}
public InputStream getInputStream () throws Exception {
switch (type) {
case "NORMAL":
return new FileInputStream(fileName);
case "HDFS":
return fileSystem.open(new Path(fileName));
default:
throw new Exception("unkonw file system");
}
}
} public static void testReadMethod(InputStreamBuilder builder, int n) throws Exception {
long time1 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod1(builder.getInputStream());
}
long time2 = System.currentTimeMillis();
System.out.println("method1,耗时:" + (time2 - time1) + " 直接读取"); long time3 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod2(builder.getInputStream());
}
long time4 = System.currentTimeMillis();
System.out.println("method2,耗时:" + (time4 - time3) + " ByteArrayOutputStream+byte[]"); long time5 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod3(builder.getInputStream());
}
long time6 = System.currentTimeMillis();
System.out.println("method3,耗时:" + (time6 - time5) + " InputStreamReader+char[8192]+StringWriter"); long time7 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod4(builder.getInputStream());
}
long time8 = System.currentTimeMillis();
System.out.println("method4,耗时:" + (time8 - time7) + " InputStreamReader+BufferedReader(char[8192])"); long time9 = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
readMethod5(builder.getInputStream());
}
long time10 = System.currentTimeMillis();
System.out.println("method5,耗时:" + (time10 - time9) + " bufferedReader+stream"); long time11 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod6(builder.getInputStream());
}
long time12 = System.currentTimeMillis();
System.out.println("method6,耗时:" + (time12 - time11) + " bufferedReader+stream+parallel"); long time13 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod7(builder.getInputStream());
}
long time14 = System.currentTimeMillis();
System.out.println("method7,耗时:" + (time14 - time13) + " Deque<byte[8196]>"); long time15 = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
readMethod8(builder.getInputStream());
}
long time16 = System.currentTimeMillis();
System.out.println("method8,耗时:" + (time16 - time15) + " ByteBuffer(2048)+LineBuffer");
} /**
* 一次性全部读取
* 不建议使用
*/
public static String readMethod1(InputStream inputStream) throws Exception {
byte[] bytes = new byte[inputStream.available()];
int size = inputStream.read(bytes);
String s = new String(bytes, 0, size, StandardCharsets.UTF_8);
// System.out.println(s.length());
inputStream.close();
return s;
} /**
* 使用ByteArrayOutputStream+自定义缓冲区,缓冲区大小可以依据文件大小而定
* 本质:ByteArrayOutputStream在write数据时,会检测容量是否满足需求,若不满足需求则会扩容,直到InputStream读取完毕
* 最佳实践:可以使用new ByteArrayOutputStream(inputStream.available()); 这样可以避免扩容时产生的时间损耗;同时按照大小调整缓冲区大小。
*/
public static String readMethod2(InputStream inputStream)throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputStream.available());
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
byteArrayOutputStream.write(buffer, 0, len);
}
String s = byteArrayOutputStream.toString(StandardCharsets.UTF_8);
// System.out.println(s.length());
byteArrayOutputStream.close();
inputStream.close();
return s;
} /**
* 使用StringWriter+org.apache.commons.io.IOUtils.copy
* 本质:该copy方法使用的InputStreamReader,每次读取char[8192]作为缓冲区,然后while循环写入StringBuffer
* InputStreamReader是将字节流按照编码转换为字符流,read方法是按编码来读取字符,而不是读取字节。
* StringWriter底层是StringBuffer,StringBuffer底层还是byte[],若超过初始设定的长度,则进行扩容
* 关键代码:AbstractStringBuilder的683行append(char str[], int offset, int len)方法
*/
public static String readMethod3(InputStream inputStream) throws Exception {
StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8);
String s = writer.toString();
// System.out.println(s.length());
writer.close();
inputStream.close();
return s;
} /**
* 使用BufferedReader+while
* 本质:BufferedReader是建立再InputStreamReader之上,读取char[8192]作为缓冲区
* readLine()方法则是将缓冲区上的字符按换行符处理成一行字符串后返回,若缓冲区读完了还没有换行符则继续读取下一批char[8192]
* BufferedReader.readLine()适用于一行一行,并有后续操作的需求,而不是读取整个文件到字符串中
*/
public static String readMethod4(InputStream inputStream) throws Exception {
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
String s;
StringBuilder sb = new StringBuilder();
while ((s = bufferedReader.readLine()) != null) {
sb.append(s).append("\n");
}
s = sb.toString();
// System.out.println(s.length());
bufferedReader.close();
reader.close();
inputStream.close();
return s;
} /**
* 使用bufferedReader+stream
* 本质:lines()方法返回一个Stream,该流的数据由迭代器生成,迭代器方法还是readList()
*/
public static String readMethod5(InputStream inputStream) throws Exception {
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
String s = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()));
// System.out.println(s.length());
bufferedReader.close();
reader.close();
inputStream.close();
return s;
} /**
* 使用bufferedReader+stream+parallel
* 同上,只是使用parallel并行计算
*/
public static String readMethod6(InputStream inputStream) throws Exception {
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
String s = bufferedReader.lines().parallel().collect(Collectors.joining(System.lineSeparator()));
// System.out.println(s.length());
bufferedReader.close();
reader.close();
inputStream.close();
return s;
} /**
* 使用google的guava
* 本质:独树一帜,不使用缓冲区,而是使用Deque<byte[8196]>作为接收byte的数据区,等全部接收完毕后,再整合成一个完整的byte[]
* 注意guava 27.0版本的该方法还是beta方法,可能会存在潜在风险
*/
public static String readMethod7(InputStream inputStream) throws Exception {
String s = new String(ByteStreams.toByteArray(inputStream), StandardCharsets.UTF_8);
return s;
} /**
* 使用google的guava的CharStreams.readLines()方法
* 本质:以ByteBuffer(2048)为缓冲区读取字符流,并使用LineBuffer作为行缓冲,底层是StringBuilder
*/
public static String readMethod8(InputStream inputStream) throws Exception {
InputStreamReader reader = new InputStreamReader(inputStream);
List<String> stringList = CharStreams.readLines(reader);
StringBuilder sb = new StringBuilder();
for (String s : stringList) {
sb.append(s).append("\n");
}
String s = sb.toString();
// System.out.println(s.length());
reader.close();
inputStream.close();
return s;
}

java-文件IO常用操作对比的更多相关文章

  1. [目录][总结] C++和Java 中的主要操作对比

    总结一些,C++ 和Java 中的一些常用操作对比,就当是自己的查询工具书啦.(暂时按随笔的更新时间排序) [Stack] c++ V.S. Java (2015.04.27) [Map]   c++ ...

  2. java文件夹相关操作 演示样例代码

    java文件夹相关操作 演示样例代码 package org.rui.io; import java.io.File; import java.io.FilenameFilter; import ja ...

  3. Java - 文件(IO流)

    Java - 文件 (IO)   流的分类:     > 文件流:FileInputStream | FileOutputStream | FileReader | FileWriter     ...

  4. java文件的读写操作

    java文件的读写操作主要是对输入流和输出流的操作,由于流的分类很多,所以概念很容易模糊,基于此,对于流的读写操作做一个小结. 1.根据数据的流向来分: 输出流:是用来写数据的,是由程序(内存)--- ...

  5. SFTP上传下载文件、文件夹常用操作

    SFTP上传下载文件.文件夹常用操作 1.查看上传下载目录lpwd 2.改变上传和下载的目录(例如D盘):lcd  d:/ 3.查看当前路径pwd 4.下载文件(例如我要将服务器上tomcat的日志文 ...

  6. java 文件的读写操作

    java  文件的读写操作 一.读: public String getSetting() { HttpServletRequest request=org.apache.struts2.Servle ...

  7. Java 文件 IO 操作

    window 路径分割符: \ 表示 windows 系统文件目录分割符 java 代码在 windows 下写某个文件的话需要下面的方式 D:\\soft\\sdclass.txt  其中一个单斜杠 ...

  8. Java文件IO操作应该抛弃File拥抱Paths和Files

    Java7中文件IO发生了很大的变化,专门引入了很多新的类: import java.nio.file.DirectoryStream;import java.nio.file.FileSystem; ...

  9. java文件IO操作

    package com.io; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream ...

  10. Java文件IO操作应该抛弃File拥抱Path和Files

    Java7中文件IO发生了很大的变化,专门引入了很多新的类: import java.nio.file.DirectoryStream;import java.nio.file.FileSystem; ...

随机推荐

  1. Docker中apt-get update失败解决方案

    一.更换apt的镜像源 1. 进入目录 cd /etc/apt 2. 备份源文件 cp /etc/apt/sources.list /etc/apt/sources.list.bak 3. 更改镜像源 ...

  2. easyUI实现查询条件传递给后端并自动刷新表格的两种方法

    easyUI实现查询条件传递给后端并自动刷新表格的两种方法 用ajax的post函数传递参数,再通过loadData方法将数据初始化到表格中 js代码: //根据id搜索 $("#stand ...

  3. 使用 DirectSound 录制麦克风音频

    使用 DirectSound 录制麦克风音频 本文所有代码均可在以下仓库找到 https://gitcode.net/PeaZomboss/learnaudios 目录是demo/dscapture ...

  4. bat想要写一个卸载软件的脚本,最后宣布失败[未完待续...]

    find 的用法:双引号,搜索内容是英文也要用双引号 C:\Users\clouder\Desktop\yanna>find '小智' products.txt FIND: 参数格式不正确 C: ...

  5. openfoam UPstream类探索(二)

    前言 接上次的博文,本篇补全以下几个函数的介绍: Pstream::nProcs() Pstream::parRun() UPstream::exit() 简述几个常用的函数如下: Pstream:: ...

  6. 开发者进阶必备的9个Tips & Tricks!

    优秀的开发人员市场前景是十分广阔的,但想找到一份理想的工作,仅有代码知识是不够的.优秀的工程师应该是一个终身学习者.问题的创造性解决者,着迷于整个软件世界.要成为一名优秀的开发者,应该具备哪些品质并做 ...

  7. GmSSL3.0密码算法库

    GmSSL3.0密码算法库 一.开发背景 GmSSL 3.0版本具有更快.更小.更安全的特点,相比于GmSSL 2.0我们主要从以下方向进行改进: 采用CMake替代目前基于Perl的构建系统 支持L ...

  8. 爬虫下载rockchip的规格书

    #file-name: pdf_download.py import os import requests from bs4 import BeautifulSoup def download_fil ...

  9. mybatis动态标签——foreach批量添加和删除

    <!-- [foreach标签] collection:设置要循环的数组或集合 item:用一个字符串表示数组或集合中的每一个数据 separator:设置每次循环的数据之间的分隔符 open: ...

  10. 重写父类的ToString

    我们任何对象调用ToString的时候,打出来的都是这个类的命名空间的名字 using System; using System.Collections.Generic; using System.L ...