Java Random File Access: Вземете отместване на байта от началото на реда

Трябва произволно да получа достъп до конкретни записи в текстов (ASCII) файл и след това да чета от там, докато бъде намерена конкретна „стоп последователност“ (разделител на запис). Файлът съдържа многоредови записи и всеки запис е разделен с разделител. Всеки запис също така отнема различно количество редове! Това е общоизвестен файлов формат в конкретната област на експертиза и не може да бъде променян.

Искам да индексирам файла, за да мога бързо да премина към искан запис.

При подобни въпроси като

Как да получите достъп до низ във файл по позиция в Java

и връзките в него, отговорът винаги препраща към метода seek() на различни класове като RandomAccessFile. Знам за това!

Въпросът, който имам, е как да получа отместването, необходимо за търсене! (индексиране на файла)

BufferedReader няма метод getFilePointer() или друг, за да получи текущото отместване на байта от началото на файла. RandomAccessFile има метод readLine(), но неговата производителност е повече от ужасна. За моя случай изобщо не може да се използва.

Ще трябва да чета файла ред по ред и всеки път, когато бъде намерен разделителят на запис, трябва да получа отместването на байта. Как мога да постигна това?


person beginner_    schedule 20.09.2013    source източник
comment
Има ли опция за разделяне на файла на записи и съхраняване на записите в база данни? Може би някакъв вкус на NoSQL?   -  person Viktor Seifert    schedule 20.09.2013
comment
Кеширайте индексите: или в паметта, в някакъв обект на притежател на индекс; или на някакво постоянно хранилище, като база данни, отделен файл или заглавка; в зависимост от това за какво ви трябва това.   -  person blgt    schedule 20.09.2013
comment
@ViktorSeifert трябва да бъде просто решение като самостоятелно   -  person beginner_    schedule 22.09.2013
comment
@blgt индексът е кеширан в паметта, но е планирано добавяне на поддръжка за запазването и възстановяването му. Но проблемът е да се получи донякъде приемлива производителност при изграждането му.   -  person beginner_    schedule 22.09.2013


Отговори (4)


Можете да опитате да подкласирате класа BufferedReader, за да запомните позицията за четене. Но няма да имате функцията за търсене.

Както споменахте, записът може да бъде многоредов, но всички записи са разделени от последователност за спиране. Като се има предвид това, можете да използвате RandomAccessFile така:

  1. имат байтов буфер byte b[] с размер да кажем 8k (това е от съображения за производителност)

  2. прочетете 8k от файла в този буфер и се опитайте да намерите разделителя, ако не бъде намерен, прочетете друг блок от 8k, но преди това добавете данните към някаква StringBuilder или друга структура.

  3. когато сте намерили разделителя, позицията на разделителя се дава от броя обработени байтове от последния намерен разделител (трябва да направите малко проста математика).

Трудната част ще бъде, ако разделителят на записа е по-дълъг от 1 знак, но това би трябвало да е голям проблем.

person Claudiu    schedule 20.09.2013
comment
разделителят е 4 знака. И теоретично (но много малко вероятно и също глупаво) може да се появи в запис, което означава, че за да бъдем 100% сигурни, трябва да са 4-те знака на отделен ред. - person beginner_; 20.09.2013
comment
така че основно вашият разделител е newline + your 4 characters. не би трябвало да е проблем. всеки път, когато запис е по-голям от 8k и вече е добавен част от тях към StringBuilder, който споменах в стъпка #2, можете да опитате да видите дали това StringBuilder завършва с началото на разделителя и новопрочетените данни започват с останалата част разделител. Това може да е метод с няколко реда код. Кажете ми дали това е ясно. - person Claudiu; 20.09.2013

След много по-нататъшно търсене в гугъл, опити и грешки и много други измислих решение, което просто обвива RandomAccessFile и разкрива всички методи. Методът readLine() обаче беше много подобрен чрез говорене на този от BufferedReader с малки корекции. Изпълнението вече е идентично с него.

Този така наречен клас OptimizedRandomAccessFile буферира извикванията readLine(), докато не се извикват други методи, изискващи или засягащи позицията във файла. например в:

OptimizedRandomAccessFile raf = new OptimizedRandomAccessFile(filePath, "r");
String line = raf.readLine();
int nextByte = raf.read();

nextByte ще съдържа първия байт от следващия ред във файла.

Пълният код може да бъде намерен на bitbucket.

person beginner_    schedule 22.09.2013

Бих използвал следната последователност от декоратори на java.io:

   InputStreamReader    <-- reader, the top reader
   CountingInputStream  <-- cis, stores the position (from Google Guava)
   BufferedInputStream  <-- speeds up file reading
   FileInputStream

След това четете от този най-добър четец чрез прилагане на readLine() метод, който чете знаци един по един до разделител на редове. Не бих използвал BufferedReader, тъй като би развалил текущата позиция чрез четене на пълен буфер с фиксиран размер.

Така че, ако разбера правилно проблема, алгоритъмът е толкова прост, колкото

  1. long lineStartPosition = cis.getCount();
  2. String s = readLine(reader);
  3. if(s.equals(DELIMITER)) { storeToIndex(lineStartPosition, recordData); }
person Andrey Chaschev    schedule 20.09.2013
comment
InputStreamReader буферира при някои условия - За да се даде възможност за ефективно преобразуване на байтове в знаци, повече байтове могат да бъдат прочетени напред от базовия поток, отколкото са необходими за изпълнение на текущата операция за четене. - person Jeow Li Huan; 07.03.2014

Можете да прочетете целия файл с данни и да запишете къде е намерен разделителят и да запишете тези метаданни в различен файл. Сега можете да използвате метаданните, за да навигирате във файла (прескачане от един разделител към друг). Всеки път, когато файлът с данни бъде модифициран, ще трябва да го сканирате отново и да генерирате повторно метаданните.

person aUserHimself    schedule 20.09.2013