Java.io:性能调优

4

我有一个大约4MB的文件,该文件是一个只包含普通键盘字符的ASCII文件。我尝试了java.io包中的许多类来将文件内容读取为字符串。如果逐个字符地进行读取(使用FileReader和BufferedReader),需要大约40秒钟;而使用java.nio包(FileChannel和ByteBuffer)读取内容需要大约25秒钟。据我所知,这需要略微较长的时间。是否有人知道任何可以将此时间消耗减少到约10秒左右的方法?甚至像创建C语言文件读取器并从java中调用这样的解决方案也可以。我使用以下代码片段在22秒内读取了这个4MB的文件 -

public static String getContents(File file) {
    try {
        if (!file.exists() && !file.isFile()) {
            return null;
        }
        FileInputStream in = new FileInputStream(file);
        FileChannel ch = in.getChannel();
        ByteBuffer buf = ByteBuffer.allocateDirect(512);            
        Charset cs = Charset.forName("ASCII");          
        StringBuilder sb = new StringBuilder();
        int rd;
        while ((rd = ch.read(buf)) != -1) {
            buf.rewind();
            CharBuffer chbuf = cs.decode(buf);
            for (int i = 0; i < chbuf.length(); i++) {
                sb.append(chbuf.get());
            }
            buf.clear();
        }
        String contents = sb.toString();
        System.out.println("File Contents:\n"+contents);
        return contents;
    } catch (Exception exception) {
        System.out.println("Error:\n" + exception.getMessage());
        return null;
    }
}

你为什么要逐字节读取?你知道文件的大小- 分配一个足够大的字节数组来保存文件内容,并使用read()完全读取它。 - mcfinnigan
3个回答

5

我无法想象你的硬件可能是什么,但读取一个4MB文件应该不到0.1秒。

一种快速读取整个文件的方法是将其读入byte[]中。

public static String readFileAsString(File file) {
    try {
        DataInputStream in = new DataInputStream(FileInputStream(file));
        byte[] bytes = new byte[(int) file.length()];
        in.readFully(bytes);
        in.close();
        return new String(bytes, 0); // ASCII text only.

    } catch (FileNotFoundException e) {
        return null;
    } catch (IOException e) {
        System.out.println("Error:\n" + e.getMessage());
        return null;
    }
}

public static void main(String... args) throws IOException {
    File tmp = File.createTempFile("deleteme", "txt");
    tmp.deleteOnExit();

    byte[] bytes = new byte[4 * 1024 * 1024];
    Arrays.fill(bytes, (byte) 'a');
    FileOutputStream fos = new FileOutputStream(tmp);
    fos.write(bytes);
    fos.close();

    long start = System.nanoTime();
    String s = readFileAsString(tmp);
    long time = System.nanoTime() - start;
    System.out.printf("Took %.3f seconds to read a file with %,d bytes%n",
            time / 1e9, s.length());
}

打印
Took 0.026 seconds to read a file with 4,194,304 bytes

如果您想更快地读取文件,我建议使用内存映射文件,因为它只需要不到10毫秒的时间,但在这种情况下有点过头了。


1
控制台更新屏幕的速度很慢。如果您正在使用MS DOS控制台,则非常缓慢。 - Peter Lawrey
@mcfinnigan,当System.out被重定向到文件时,并不会变慢。尝试使用java MyClass > output.txt命令,你会发现它非常快。 - Peter Lawrey
这适用于大多数流,但不适用于文件。更好的方法是使用DataInputStream进行编写。注意:write()也有同样的问题。 - Peter Lawrey
对于大型文件,我不会使用内存映射文件,因为虚拟机永远不会释放。我的评论中没有双重否定。@PeterLawrey - user207421
@PeterLawrey 没错。在一个错误报告中,这个问题已经存在了很长时间。当MBB所属的FileChannel被关闭时,MBB并没有被释放。根据错误报告,甚至连通过GC释放MBB的明确定义的时间都不存在。 - user207421
显示剩余8条评论

2
  1. 在这里使用直接字节缓冲区没有任何好处。
  2. 您的缓冲区大小为512太小了,至少要使用4096。
  3. 在这里使用NIO并没有真正的好处。由于这是文本,我会使用BufferedReader。
  4. 您读取整个文件到内存的基本目标有缺陷,它不可扩展,并且已经使用了过多的内存。您应该设计一种逐行处理文件的策略。

1

你可以增加缓冲区大小,将其设置为2048或4096字节。

不要使用本地API,因为你无法获得Java的编译时类型检查功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接