相关历史文章(阅读本文之前,您可能需要先看下之前的系列 )
国内最全的Spring Boot系列之三
精度不够,滑动时间来凑「限流算法第二把法器:滑动时间窗口算法」- 第301篇
没有预热,不叫高并发「限流算法第三把法器:令牌桶算法」- 第302篇
水满自溢「限流算法第四把法器:漏桶算法」- 第303篇
一分钟get:缓存穿透、缓存击穿、缓存雪崩 - 第304篇
布隆过滤器Bloom Filter竟然让我解决了一个大厂的问题 - 第305篇
在前面的文章《布隆过滤器Bloom Filter竟然让我解决了一个大厂的问题》大厂面试题中,还隐含着一个问题,已经被我们的粉丝提前嗅探出来了:“如何用4g内存读取298g的文件(a和b文件)“。
这里我们把它抽象成,Java如何读取大文件?
师傅:我可爱的小徒儿,你知否?
悟纤:知否?知否?应是绿肥红瘦。你说我知否?
师傅:徒儿,你这是什么乱七八糟的,你不说,我怎知你知否?
悟纤:那徒儿,今天来给你展示下我深厚的功力了。
师傅:来,请开始你的表演。
一、内存读取法:简单明了,不玩阴的
此方法的思路很简单,就是把文件直接读取到内存中,然后进行操作。
1.1 方法一:使用java.nio.file.Files读取文本文件
使用Files类将文件的所有内容读入字节数组。Files类还有一个方法可以读取所有行到字符串列表。Files类是在Java 7中引入的,如果想加载所有文件内容,使用这个类是比较适合的。只有在处理小文件并且需要加载所有文件内容到内存中时才应使用此方法。
public static void readFileByFiles(String pathname) {
Path path = Paths.get(pathname);
try {
/*
* 使用readAllLines的时候,小文件可以很快读取.
* 那么更大的文件,读取的肯定会爆了。
*/
//List<String> lines = Files.readAllLines(path);
byte[] bytes = Files.readAllBytes(path);
String str = new String(bytes);
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
}
对于小文件,轻松就可以读取进来;
对于大文件就会抛出如下异常:
或者是:
结论:小文件可以使用这种方式;读取大文件,不能使用。
二、IO逐行读取法:循序渐进,好舒服
2.1 方法二:使用java.io.FileReader类
可以使用FileReader获取BufferedReader,然后逐行读取文件,FileRead也有读取char的方法,当然这样的读取方式效率很低很低了。
public static void readFileByFileReader(String pathname) {
File file = new File(pathname);
FileReader fileReader;
BufferedReader bufferedReader;
try {
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
String line;
StringBuffer buffer = new StringBuffer();
while((line = bufferedReader.readLine()) != null){
// 一行一行地处理...
//System.out.println(line);
//处理字符串,并不会将字符串保存真正保存到内存中
// 这里简单模拟下处理操作.
buffer.append(line.substring(0,1));
}
System.out.println("buffer.length:"+buffer.length());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//TODO close处理.
}
}
对于大文件可以逐行读取,没啥问题,测试了下:3.46G 耗时:11秒
注意:这里本质是使用了BufferedReader的缓冲,如果是使用的fileReader读取char的方式,那么时间会更久。
2.2方法三:使用java.io.BufferedReader
如果想逐行读取文件并对它们进行处理,那么BufferedReader是非常合适的。它适用于处理大文件,也支持编码。BufferedReader是同步的,因此可以安全地从多个线程完成对BufferedReader的读取操作。BufferedReader的默认缓冲区大小为:8KB。
public static void readFileByBufferedReader(String pathname) {
File file = new File(pathname);
BufferedReader reader = null;
FileInputStream fileInputStream = null;
InputStreamReader inputStreamReader = null;
try {
//使用BufferedReader,每次读入1M数据.减少IO.如:
fileInputStream = new FileInputStream(file);
inputStreamReader = new InputStreamReader(fileInputStream, Charset.defaultCharset());
reader = new BufferedReader(inputStreamReader,1*1024*1024);
String tempString = null;
StringBuffer buffer = new StringBuffer();
while( (tempString = reader.readLine()) != null) {
//System.out.println(tempString);
//处理字符串,并不会将字符串保存真正保存到内存中
// 这里简单模拟下处理操作.
buffer.append(tempString.substring(0,1));
}
System.out.println("buffer.length:"+buffer.length());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//TODO close处理
}
}
逐行读取,可以处理大文件,测试3.46G 耗时:10秒
2.3方法四:使用Scanner读取文本文件
如果要逐行读取文件或基于某些java正则表达式读取文件,则可使用Scanner类。
Scanner类使用分隔符模式将其输入分解为标记,分隔符模式默认匹配空格。然后可以使用各种下一种方法将得到的标记转换成不同类型的值。Scanner类不同步,因此不是线程安全的。
public static void readFileByScanner(String filePath) {
FileInputStream inputStream = null;
Scanner sc = null;
try {
inputStream = new FileInputStream(filePath);
sc = new Scanner(inputStream, "UTF-8");
StringBuffer buffer = new StringBuffer();
while (sc.hasNextLine()) {
String line = sc.nextLine();
//System.out.println(line);
//处理字符串,并不会将字符串保存真正保存到内存中
// 这里简单模拟下处理操作.
buffer.append(line.substring(0,1));
}
System.out.println("buffer.length:"+buffer.length());
} catch (Exception e) {
e.printStackTrace();
} finally {
//TODO close处理
}
}
逐行处理,可以处理大文件,测试3.46G 耗时:57秒
2.4 方法五:使用RandomAccessFile读取文本文件
Java中的RandomAccessFile提供了对文件的读写功能。RandomAccessFile 虽然属于http://java.io下的类,但它不是InputStream或者OutputStream的子类;它也不同于FileInputStream和FileOutputStream。 FileInputStream 只能对文件进行读操作,而FileOutputStream 只能对文件进行写操作;但是RandomAccessFile 与输入流和输出流不同之处就是RandomAccessFile可以访问文件的任意地方同时支持文件的读和写,并且它支持随机访问。RandomAccessFile包含InputStream的三个read方法,也包含OutputStream的三个write方法。同时RandomAccessFile还包含一系列的readXxx和writeXxx方法完成输入输出。
public static void readFileByRandomAccessFile(String pathname) {
RandomAccessFile randomAccessFile = null;
String str;
try {
randomAccessFile = new RandomAccessFile(pathname, "r");
StringBuffer buffer = new StringBuffer();
while ((str = randomAccessFile.readLine()) != null) {
//System.out.println(str);
//处理字符串,并不会将字符串保存真正保存到内存中
// 这里简单模拟下处理操作.
buffer.append(str.substring(0,1));
}
System.out.println("buffer.length:"+buffer.length());
} catch (IOException e) {
e.printStackTrace();
}finally {
//TODO close处理
}
}
逐行读取,可以读取大文件,测试3.46G 耗时:很长...
三、NIO逐行读取法:新的姿势,新的体验
3.1 方法五:使用FileChannel读取文本
顾名思义,FileChannel就是连接到文件的Channel。使用FileChannel,你可以读取文件数据,以及往文件里面写入数据。Java NIO的FileChannel是使用标准Java IO读取文件的一种替代方案。
public static void readFileFileChannel(String pathname) {
File file = new File(pathname);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileChannel fileChannel = fileInputStream.getChannel();
int capacity = 1*1024*1024;//1M
ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
StringBuffer buffer = new StringBuffer();
while( fileChannel.read(byteBuffer) != -1) {
//读取后,将位置置为0,将limit置为容量, 以备下次读入到字节缓冲中,从0开始存储
byteBuffer.clear();
byte[] bytes = byteBuffer.array();
String str = new String(bytes);
//System.out.println(str);
//处理字符串,并不会将字符串保存真正保存到内存中
// 这里简单模拟下处理操作.
buffer.append(str.substring(0,1));
}
System.out.println("buffer.length:"+buffer.length());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//TODO close处理.
}
}
逐行读取,可以读取大文件,测试3.46G 耗时:3秒,测试6.5G 耗时:6秒
四、悟纤小结
小结下:
(1)java底层天然支持大文件的读取。
(2)常用的方式就是BufferedReader、FileChannel,FileChannel已经是很快了,测试3.46G 耗时:3秒,测试6.5G 耗时:6秒,测试13G 耗时15秒,那么130G,那么也就150秒左右了。
(3)NIO在大文件上的操作很占优势。(NIO为什么会这么快呢?)
师傅:不错、不错,总结的很赞了。
徒儿:都是师傅教学有方。
师傅:还是徒儿自己好学、喜欢研究的结果。现在是实现了大文件的读取,那么有没有更快的方式呐?。
徒儿:那是,不知道师傅有何高招呐?
师傅:必修得有高招,不然怎么做你师傅呐,预知详情下回分解。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
学院中有 Spring Boot相关的课程:
à悟空学院: https:// t.cn/Rg3fKJD
SpringBoot视频: http:// t.cn/A6ZagYTi
Spring Cloud视频: http:// t.cn/A6ZagxSR
SpringBoot Shiro视频: http:// t.cn/A6Zag7IV
SpringBoot交流平台: https:// t.cn/R3QDhU0
SpringData和JPA视频: http:// t.cn/A6Zad1OH
SpringSecurity5.0视频: http:// t.cn/A6ZadMBe
Sharding-JDBC分库分表实战: http:// t.cn/A6ZarrqS
分布式事务解决方案「手写代码」: http:// t.cn/A6ZaBnIr
JVM内存模型和性能调优: http:// t.cn/A6wWMVqG