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