Java:InputStream 读取大文件太慢
Posted
技术标签:
【中文标题】Java:InputStream 读取大文件太慢【英文标题】:Java: InputStream too slow to read huge files 【发布时间】:2012-05-06 20:09:37 【问题描述】:我必须一个字符一个字符地读取一个 53 MB 的文件。当我使用 ifstream 在 C++ 中执行此操作时,它会在几毫秒内完成,但使用 Java InputStream 则需要几分钟。 Java 这么慢是正常的还是我错过了什么?
另外,我需要用 Java 完成程序(它使用 servlet,我必须从中调用处理这些字符的函数)。我在想也许用 C 或 C++ 编写文件处理部分,然后使用 Java Native Interface 将这些函数与我的 Java 程序接口......这个想法如何?
谁能给我任何其他提示...我真的需要更快地阅读文件。我尝试使用缓冲输入,但它仍然没有提供接近 C++ 的性能。
已编辑:我的代码跨越多个文件,而且很脏,所以我给出了概要
import java.io.*;
public class tmp
public static void main(String args[])
try
InputStream file = new BufferedInputStream(new FileInputStream("1.2.fasta"));
char ch;
while(file.available()!=0)
ch = (char)file.read();
/* Do processing */
System.out.println("DONE");
file.close();
catch(Exception e)
【问题讨论】:
向我们展示您的代码。如果不了解您的工作方式,我们无法猜测您的问题。 你在使用BufferedInputStream
吗?你应该在BufferedReader
上使用它。您的访问模式是否可以使用java.nio
对文件的部分进行内存映射?具体来说,当您说“char
by char
”时,您是否足够了解编码以处理 char
s 其字节序列可能分布在多个内存映射段中?
没有办法只读取那些 53M 字符而不做任何其他事情可能需要超过几秒钟,缓冲或不缓冲。肯定还有别的东西。
逐字阅读可能是您的问题。
您错误地使用了file.available()
。试试这个,while((ch = (char)file.read()) >= 0)
并删除 ch = (char)file.read();
【参考方案1】:
我使用 183 MB 的文件运行此代码。它打印“Elapsed 250 ms”。
final InputStream in = new BufferedInputStream(new FileInputStream("file.txt"));
final long start = System.currentTimeMillis();
int cnt = 0;
final byte[] buf = new byte[1000];
while (in.read(buf) != -1) cnt++;
in.close();
System.out.println("Elapsed " + (System.currentTimeMillis() - start) + " ms");
【讨论】:
不错。另外,我需要逐字符处理文件。因此,我不会从文件中读取单个字符,而是从缓冲区中检索它,如果它用完,请再次填充它。非常感谢:) 是的,我认为 Java 在方法调度方面陷入了困境,而 C++ 甚至可能内联调用。有时在调用足够多之后,HotSpot 也会内联调用,但我不能确定这种情况。 @MarkoTopolnik 除了调用 InputStream.available() 5300 万次(即 5300 万次冗余系统调用)之外,这里没有证据表明 Java 在任何事情上都“陷入困境”。由于他使用的是 BufferedInputStream,实际读取文件的系统调用次数为 53/8192 百万,因此调用 available() 是一个巨大的开销。 @MarkoTopolnik 我用BufferedInputStream.read(byte[])
得到 676 毫秒; FileInputStream.read(byte[])
为 2441 毫秒,BufferedInputStream.read()
为 610 毫秒(一次一个字节)。具有随机内容的不同文件(以避免缓存),全部为 183MB,1000 字节缓冲区。 Java 6. 多次运行的结果非常一致。没有 50 倍问题的迹象。
@EJP 好的,所以我们可能在谈论 OS X 上的糟糕实现。在阅读了您的结果后,我仔细检查了我的结果。这是真的。【参考方案2】:
我会试试这个
// create the file so we have something to read.
final String fileName = "1.2.fasta";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(new byte[54 * 1024 * 1024]);
fos.close();
// read the file in one hit.
long start = System.nanoTime();
FileChannel fc = new FileInputStream(fileName).getChannel();
ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
while (bb.remaining() > 0)
bb.getLong();
long time = System.nanoTime() - start;
System.out.printf("Took %.3f seconds to read %.1f MB%n", time / 1e9, fc.size() / 1e6);
fc.close();
((DirectBuffer) bb).cleaner().clean();
打印
Took 0.016 seconds to read 56.6 MB
【讨论】:
DirectBuffer 符号未找到。所以我删除了最后一行,但运行它抛出了 java.nio.BufferUnderflowException。 (53.4 MB 文件) 我一次读取 8 个字节以加快速度,如果长度不是 8 的倍数,这是不好的。您可以改用bb.get()
。 DirectBuffer 位于 sun.nio.ch
中,这使其成为可以删除的内部使用 API。【参考方案3】:
使用BufferedInputStream
:
InputStream buffy = new BufferedInputStream(inputStream);
【讨论】:
我也使用了 BufferedInputStream。 InputStream fh = new BufferedInputStream(new FileInputStream("file"));【参考方案4】:如上所述,使用 BufferedInputStream。你也可以使用 NIO 包。请注意,对于大多数文件,BufferedInputStream 的读取速度与 NIO 一样快。但是,对于非常大的文件,NIO 可能会做得更好,因为您可以进行内存映射文件操作。此外,NIO 包执行可中断 IO,而 java.io 包则没有。这意味着如果你想从另一个线程取消操作,你必须使用 NIO 来使其可靠。
ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE);
FileChannel fileChannel = fileInputStream.getChannel();
int readCount = 0;
while ( (readCount = fileChannel.read(buf)) > 0)
buf.flip();
while (buf.hasRemaining())
byte b = buf.get();
buf.clear();
【讨论】:
我认为内存映射文件对顺序读取没有任何好处。 @MarkoTopolnik 他们对任何事情的时间收益都不过 20%,但我不知道您为什么认为顺序阅读是一种特殊情况。它不是。磁盘仍会进行预读,就像使用流或读取器时一样。 @EJP 是的,但是预读无论如何都是在较低级别完成的(即使在磁盘电子设备中,也在磁盘缓存实现中)。 @MarkoTopolnik 为什么 MM 文件对顺序读取没有好处? @EJP MM 文件主要是为了方便随机访问读取,因为它们提供了一个简单的 API 来访问文件,就好像它是 RAM 中的一个数组一样。如果您所做的只是从上到下运行 MM 文件,那么您只会给内存管理器施加压力,而不会从范例中获得任何好处。以上是关于Java:InputStream 读取大文件太慢的主要内容,如果未能解决你的问题,请参考以下文章
在Java中读取3GB的非常大的csv文件的内存有效方法是什么?