网站首页 > java教程 正文
一般来说读取文件行的标准方法是保存在内存中读取。 Guava 和 Apache Commons IO 都提供了一种快速方法来做到这一点。
Files.readLines(new File(path), Charsets.UTF_8);
FileUtils.readLines(new File(path));
这种方法的问题是所有文件行都保存在内存中 -如果文件足够大,这将很快导致OutOfMemoryError 。
例如,当读取 1Gb 文件的时候,你就会发现:
@Test
public void givenUsingGuava_whenIteratingAFile_thenWorks() throws IOException {
String path = ...
Files.readLines(new File(path), Charsets.UTF_8);
}
首先消耗少量内存:(消耗约 0 Mb)
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 128 Mb
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 116 Mb
然而,在处理完整文件后,我们最终得到:(消耗约 2 Gb)。
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 2666 Mb
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 490 Mb
这意味着该进程消耗了大约 2.1 GB 的内存 - 原因很简单 - 文件的行现在都存储在内存中。
很明显,将文件内容保留在内存中将很快耗尽可用内存。
更重要的是,我们通常不需要一次将文件中的所有行都存储在内存中- 相反,我们只需要能够迭代每一行,进行一些处理然后将其丢弃。所以,读取大文件正确方式应该是 迭代这些行而不将它们全部保存在内存中。
下面我们来看一下在Java中有哪些方式可以高效读取大文件。
- 使用Scanner类
可以使用java.util.Scanner来运行文件的内容并逐读取行内容。
FileInputStream inputStream = null;
Scanner sc = null;
try {
inputStream = new FileInputStream(path);
sc = new Scanner(inputStream, "UTF-8");
while (sc.hasNextLine()) {
String line = sc.nextLine();
// System.out.println(line);
}
// note that Scanner suppresses exceptions
if (sc.ioException() != null) {
throw sc.ioException();
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (sc != null) {
sc.close();
}
}
该解决方案将迭代文件中的所有行,允许处理每一行而不保留对它们的引用。总之,不将行保留在内存中:(消耗约 150 Mb)。
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 763 Mb
[main] INFO org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 605 Mb
使用BufferedReader类
此类提供了readLine()方法来缓冲字符,简化了读取文件的过程,该方法会逐行读取给定文件的内容。
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
while (br.readLine() != null) {
// do something with each line
}
}
BufferedReader通过逐块读取文件并将块缓存在内部缓冲区中来减少 I/O 操作的数量。
与Scanner相比,它表现出更好的性能,因为它只专注于数据检索而不进行解析。
使用Files.newBufferedReader()
还可以使用Files.newBufferedReader()方法来实现相同的目的。
try (BufferedReader br = java.nio.file.Files.newBufferedReader(Paths.get(fileName))) {
while (br.readLine() != null) {
// do something with each line
}
}
此方法提供了另一种返回BufferedReader实例的方法。
使用SeekableByteChannel
SeekableByteChannel提供读取和操作给定文件的通道。由于它由自动调整大小的字节数组支持,因此它的性能比标准 I/O 类更快。
try (SeekableByteChannel ch = java.nio.file.Files.newByteChannel(Paths.get(fileName), StandardOpenOption.READ)) {
ByteBuffer bf = ByteBuffer.allocate(1000);
while (ch.read(bf) > 0) {
bf.flip();
// System.out.println(new String(bf.array()));
bf.clear();
}
}
该接口附带read()方法,该方法将字节序列读取到ByteBuffer表示的缓冲区中。
通常,flip()方法使缓冲区准备好再次写入。与此同时clear()会重置并清除缓冲区。
这种方法的唯一缺点是我们需要使用allocate()方法显式指定缓冲区大小。
使用Stream API
同样,我们可以使用Stream API来读取和处理文件的内容。
在这里,我们将使用Files类,它提供lines()方法来返回String元素流。
请注意,文件是延迟处理的,这意味着在给定时间只有部分内容存储在内存中。
try (Stream<String> lines = java.nio.file.Files.lines(Paths.get(fileName))) {
lines.forEach(line -> {
// do something with each line
});
}
使用 Apache Commons IO 进行流式传输
也可以通过该库提供的自定义LineIterator来实现相同的效果。
LineIterator it = FileUtils.lineIterator(theFile, "UTF-8");
try {
while (it.hasNext()) {
String line = it.nextLine();
// do something with line
}
} finally {
LineIterator.closeQuietly(it);
}
由于整个文件并不完全在内存中 - 这也会导致相当低的内存消耗:(消耗约 150 Mb)。
[main] INFO o.b.java.CoreJavaIoIntegrationTest - Total Memory: 752 Mb
[main] INFO o.b.java.CoreJavaIoIntegrationTest - Free Memory: 564 Mb
- 总结
在处理大型文件时,我们需要采取一些策略来避免迭代和耗尽可用内存。通过使用流式处理、分块处理等技术,我们可以更有效地利用系统资源,并快速、准确地处理大型文件。以上这些方式处理大型文件时非常有用。
猜你喜欢
- 2024-10-01 「每日分享」内存文件映射方式读取超大文件踩坑题解析
- 2024-10-01 尚学堂百战程序员之读写配置文件教程
- 2024-10-01 Spark中读写不同类型文件(如何改文件的读写类型)
- 2024-10-01 Java中文件使用流操作基础知识(java文件流不关闭的后果)
- 2024-10-01 Java 读取txt文件生成Word文档(java读取txt文件存为字符串)
- 2024-10-01 零基础编程培训系列JAVA入门课程第十一讲Java文件处理
- 2024-10-01 SpringBoot读取.yml配置文件最常见的两种方式
- 2024-10-01 java IO流读取文件并统计文件中各个字符出现的次数
- 2024-10-01 Java读取配置文件config.properties
- 2024-10-01 常见Java问题及笔试题(十八)——说一说代码中读取文件的事
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)