Самый быстрый способ прочитать строку в файле
Я использую 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")))