Самый быстрый способ прочитать строку в файле

Я использую RandomAccessFile читать некоторые данные из большого файла.RandomAccessFile есть метод seek который указывает курсор на определенную часть файла, которую я хочу прочитать всю строку. Чтобы прочитать эту строку я использую readLine() метод.

Я прочитал весь этот файл раньше, а затем создал индекс, который позволяет мне получить доступ к началу любой строки с seek метод. Этот индекс работает нормально. Я создал этот индекс на основе этого ответа: /questions/27362809/kak-ya-mogu-sozdat-i-poluchit-dostup-k-indeksu-chtobyi-perejti-v-opredelennuyu-pozitsiyu-bolshogo-fajla-v-java/27362821#27362821

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

Я прочитал это FileChannel с MappedByteBuffer хороший вариант для быстрого чтения файлов, но я не нашел ни одного решения, которое бы делало то, что я хочу.

PS: линии имеют разную длину, и я не знаю этой длины.

У кого-нибудь есть хорошее решение?

Редактировать:

Файл, который я хочу прочитать, имеет следующий формат: ключ\tзначение

Индекс - это хэш-карта, в которой все ключи этого файла были ключами, а значения - байтовой позицией (Long).

Давайте предположим, что я хочу перейти на строку с ключом "foo", тогда я должен искать позицию значения, например:

raf.seek(index.get("foo"))

Если я использую raf.readLine() возврат будет всей строкой с ключом "foo".

Но я не хочу использовать RandomAccessFile для этой работы, потому что это слишком медленно.

Вот как я сейчас поступаю в Scala:

val raf = new RandomAccessFile(file,"r")  
raf.seek(position.get(key))
println(raf.readLine)
raf.close

1 ответ

Если вам уже нужно один раз прочитать файл, чтобы найти индексы ключей, самое быстрое решение - прочитать строки и сохранить их в памяти. Если это по какой-то причине не работает (например, ограничения памяти), использование буферов действительно может быть хорошей альтернативой. Это набросок кода:

FileChannel channel = new RandomAccessFile("/some/file", "r").getChannel();

long pageSize = ...; // e.g. "3 GB or file size": max(channel.size(), THREE_GB); 
long position = 0;
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, pageSize);

ByteBuffer slice;
int maxLineLength = 30;
byte[] lineBuffer = new byte[maxLineLength];

// Read line at indices 20 - 25
buffer.position(20);
slice = buffer.slice();
slice.get(lineBuffer, 0, 6);
System.out.println("Starting at 20:" + new String(lineBuffer, Charset.forName("UTF8")));

// Read line at indices 0 - 10
buffer.position(0);
slice = buffer.slice();
slice.get(lineBuffer, 0, 11);
System.out.println("Starting at 0:" + new String(lineBuffer, Charset.forName("UTF8")));

Этот код также можно использовать для очень больших файлов. Просто позвони channel.map чтобы найти "страницу", где находится ваш ключ: position = keyIndex / pageSize * pageSize а затем позвоните buffer.position из этого индекса: keyIndex - position

Если у вас действительно нет способа сгруппировать доступ к одной "странице" вместе, то вам не нужно slice, Производительность не будет такой хорошей, но это позволит вам еще больше упростить код:

byte[] lineBuffer = new byte[maxLineLength];
// ...
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, keyIndex, lineLength);
buffer .get(lineBuffer, 0, lineLength);
System.out.println(new String(lineBuffer, Charset.forName("UTF8")));

Обратите внимание, что ByteBuffer не создается в куче JVM, но на самом деле это файл с отображением памяти на уровне операционной системы. (Начиная с Java 8, вы можете убедиться в этом, взглянув на исходный код и выполнив поиск sun.nio.ch.DirectBuffer в реализации).

Размер строки: лучший способ получить размер строки - сохранить ее при сканировании файла, т. Е. Использовать Map[String, (Long, Int)] вместо того, что вы используете для index сейчас. Если это не работает для вас, вы должны запустить несколько тестов, чтобы выяснить, что быстрее:

  • Просто сохраните максимальный размер строки и затем найдите разрыв строки в строке этой максимальной длины. В этом случае обратите внимание, что вы рассматриваете доступ к концу файла в своих модульных тестах.
  • Сканирование вперед с ByteBuffer.get пока вы не нажмете \n, Если у вас есть настоящие файлы Unicode, это, вероятно, не вариант, так как код Ascii для перевода строки (0x0A) может появиться в другом месте, например, в кодированном UTF-16 корейском слоге с символьным кодом 0xAC0A.

Это будет код Scala для второго подхода:

// this happens once
val maxLineLength: Long = 2000 // find this in your initial sequential scan
val lineBuffer = new Array[Byte](maxLineLength.asInstanceOf[Int])

// this is how you read a key
val bufferLength = maxLineLength min (channel.size() - index("key"))
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, index("key"), bufferLength)
var lineLength = 0 // or minLineLength
while (buffer.get(lineLength) != '\n') {
  lineLength += 1
}
buffer.get(lineBuffer, 0, lineLength - 1)
println(new String(lineBuffer, Charset.forName("UTF8")))
Другие вопросы по тегам