Использование RandomAccessFile вместе с BufferedReader для ускорения чтения файла - PullRequest
1 голос
/ 14 мая 2019

Я должен: -

  • Читать большой текстовый файл построчно.
  • Записывать позицию указателя файла после каждой прочитанной строки.
  • Остановить чтение файла, если время выполнения превышает 30 секунд.
  • Возобновить с последнего отмеченного указателя файла в новом процессе.

Что я делаю:

  1. Использование RandomAccessFile.getFilePointer () для записи указателя файла.
  2. Оберните RandomAccessFile в другой BufferedReader, чтобы ускорить процесс чтения файла в соответствии с этим ответом.
  3. Когда время превышает 30 секунд, я прекращаю читать файл. Перезапустите процесс с новым RandomAccessFile и используйте метод RandomAccessFile.seek, чтобы переместить указатель файла туда, куда я ушел.

Проблема:

Когда я читаю через BufferedReader, обернутый вокруг RandomAccessFile, кажется, что указатель файла продвигается далеко вперед за один вызов к BufferedReader.readLine (). Однако, если я использую DirectAccessFile.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

Использование Direct 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?

Ответы [ 2 ]

2 голосов
/ 14 мая 2019

Причина наблюдаемого поведения заключается в том, что, как следует из названия, BufferedReader имеет значение с буферизацией . Он считывает больший фрагмент данных за один раз (в буфер) и возвращает только соответствующие части содержимого буфера, а именно часть до следующего \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"

Как видно из расплывчатого комментария: это может быть громоздким и неудобным. Вы бы в основном заново реализовали то, что делает метод readLine в классе BufferedReader. И в этот момент я даже не хочу упоминать головные боли, которые могут вызывать различные разделители строк или наборы символов.

Для 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 должны быть закрыты должным образом, когда чтение завершено, но это зависит от того, как чтение должно быть прервано при превышении лимита времени, как описано в вопросе

0 голосов
/ 14 мая 2019

BufferedReader считывает блок данных из файла, по умолчанию 8 КБ. Поиск разрывов строк для возврата следующей строки выполняется в буфере.

Полагаю, именно поэтому вы видите огромный прирост в позиции физического файла.

RandomAccessFile не будет использовать буфер при чтении следующей строки. Он будет читать байт за байтом. Это действительно медленно.

Какова производительность, когда вы просто используете BufferedReader и запоминаете строку, с которой нужно продолжить?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...