java - 使用 RandomAccessFile 和 BufferedReader 来加速文件读取

标签 java nio filereader java-io

我必须:-

  • 逐行读取大型文本文件。
  • 在每行读取后记下文件指针位置。
  • 如果运行时间超过 30 秒,则停止文件读取。
  • 从新进程中最后记录的文件指针处恢复。

我在做什么:

  1. 使用 RandomAccessFile.getFilePointer() 记录文件指针。
  2. 将 RandomAccessFile 包装到另一个 BufferedReader 中以加快文件读取过程,如 this回答。
  3. 当时间超过 30 秒时,我停止读取文件。使用新的 RandomAccessFile 重新启动进程,并使用 RandomAccessFile.seek 方法将文件指针移动到我离开的位置。

问题:

当我阅读包裹在 RandomAccessFile 中的 BufferedReader 时,似乎文件指针在对 BufferedReader.readLine() 的一次调用中向前移动得很远。但是,如果我直接使用 RandomAccessFile.readLine(),文件指针会正确地逐步向前移动。

使用 BufferedReader 作为包装器:

    RandomAccessFile randomAccessFile = new RandomAccessFile("mybigfile.txt", "r");
BufferedReader brRafReader = new BufferedReader(new FileReader(randomAccessFile.getFD()));
while((line = brRafReader.readLine()) != null) {
    System.out.println(line+", Position : "+randomAccessFile.getFilePointer());
}

输出:

Line goes here, Position : 13040
Line goes here, Position : 13040
Line goes here, Position : 13040
Line goes here, Position : 13040

使用直接 RandomAccessFile.readLine

    RandomAccessFile randomAccessFile = new RandomAccessFile("mybigfile.txt", "r");
while((line = randomAccessFile.readLine()) != null) {
    System.out.println(line+", Position : "+randomAccessFile.getFilePointer());
}

输出:(这是预期的。每次调用 readline 时文件指针都会正确移动)

Line goes here, Position : 11011
Line goes here, Position : 11089
Line goes here, Position : 12090
Line goes here, Position : 13040

有人能告诉我,我在这里做错了什么吗?有什么方法可以使用 RandomAccessFile 加快读取过程吗?

最佳答案

观察到的行为的原因是,顾名思义,BufferedReader缓冲的。它一次读取较大的数据 block (进入缓冲区),并仅返回缓冲区内容的相关部分 - 即直到下一个\n的部分行分隔符。

我认为,从广义上讲,有两种可能的方法:

  1. 您可以实现自己的缓冲逻辑。
  2. 使用一些丑陋的反射技巧来获取所需的缓冲区偏移量

对于 1.,您将不再使用 RandomAccessFile#readLine。相反,您可以通过

进行自己的缓冲
byte buffer[] = new byte[8192];
...
// In a loop:
int read = randomAccessFile.read(buffer);
// Figure out where a line break `\n` appears in the buffer,
// return the resulting lines, and take the position of the `\n`
// into account when storing the "file pointer"

正如模糊的评论所表明的那样:这可能很麻烦且繁琐。您基本上可以在 BufferedReader 类中重新实现 readLine 方法的功能。在这一点上,我什至不想提及不同的行分隔符或字符集可能导致的麻烦。

对于 2.,您可以简单地访问存储缓冲区偏移量的 BufferedReader 字段。这在下面的示例中实现。当然,这是一个有点粗糙的解决方案,但这里提到并显示为一个简单的替代方案,具体取决于该解决方案的“可持续性”程度以及您愿意投入多少精力。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class LargeFileRead {
    public static void main(String[] args) throws Exception {

        String fileName = "myBigFile.txt";

        long before = System.nanoTime();
        List<String> result = readBuffered(fileName);
        //List<String> result = readDefault(fileName);
        long after = System.nanoTime();
        double ms = (after - before) / 1e6;
        System.out.println("Reading took " + ms + "ms "
                + "for " + result.size() + " lines");
    }

    private static List<String> readBuffered(String fileName) throws Exception {
        List<String> lines = new ArrayList<String>();
        RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "r");
        BufferedReader brRafReader = new BufferedReader(
                new FileReader(randomAccessFile.getFD()));
        String line = null;
        long currentOffset = 0;
        long previousOffset = -1;
        while ((line = brRafReader.readLine()) != null) {
            long fileOffset = randomAccessFile.getFilePointer();
            if (fileOffset != previousOffset) {
                if (previousOffset != -1) {
                    currentOffset = previousOffset;
                }
                previousOffset = fileOffset;
            }
            int bufferOffset = getOffset(brRafReader);
            long realPosition = currentOffset + bufferOffset;
            System.out.println("Position : " + realPosition 
                    + " with FP " + randomAccessFile.getFilePointer()
                    + " and offset " + bufferOffset);
            lines.add(line);
        }
        return lines;
    }

    private static int getOffset(BufferedReader bufferedReader) throws Exception {
        Field field = BufferedReader.class.getDeclaredField("nextChar");
        int result = 0;
        try {
            field.setAccessible(true);
            result = (Integer) field.get(bufferedReader);
        } finally {
            field.setAccessible(false);
        }
        return result;
    }

    private static List<String> readDefault(String fileName) throws Exception {
        List<String> lines = new ArrayList<String>();
        RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "r");
        String line = null;
        while ((line = randomAccessFile.readLine()) != null) {
            System.out.println("Position : " + randomAccessFile.getFilePointer());
            lines.add(line);
        }
        return lines;
    }
}

(注意:偏移量可能仍然显示为偏移 1,但这是由于该位置没有考虑行分隔符。如有必要,可以调整)

注意:这只是一个草图。读取完成后,RandomAccessFile 对象应正确关闭,但这取决于超出时间限制时应如何中断读取,如问题中所述

关于java - 使用 RandomAccessFile 和 BufferedReader 来加速文件读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56129666/

相关文章:

java - 方法的正确写法

java - 避免 Java 8 Files.walk(..) 终止原因 ( java.nio.file.AccessDeniedException )

Java:FileReader 和 FileWriter 不能一起工作

javascript - readAsBinaryString() 无法正常工作

java - 如何以 Media Recorder 不支持的格式进行编码?

java - 从另一个方法调用引发异常的方法时,如何避免获得 "Missing return statement"?

java 异步套接字 channel /完成处理程序

java - 将具有不同 header 的大消息写入多个接收者的效率问题

javascript - 如何在 JavaScript 中逐字节循环文件?

java - 警告 : Couldn't read data from file "image.jpg", 这会导致空 POST