Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Парсинг длинного файла


Автор: Stampede 28.4.2005, 01:01
Взываю к коллективной мудрости.

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

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

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

Во-первых, побуферный парсинг здесь не подходит, так как искомая строка может оказаться разорванной на границе буфера, и регекспом не обнаружится. Во-вторых, даже один отдельный документ может быть достаточно большим, поэтому и внутри него парсить нужно будет тоже не в памяти.

Из хороших моментов: известно, что искомая строка не может перекрывать раздел страниц, то есть можно безопасно ориентироваться на символ конца страницы как на сигнал к закрытию текущего буфера.

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

У кого какие будут идеи?

ЗЫ. Блин, ошибся топиком. Прошу перенести в Java: Общие вопросы. Спасибо.

Автор: Domestic Cat 28.4.2005, 01:35
Может этим:
http://java.sun.com/j2se/1.4.2/docs/api/java/nio/MappedByteBuffer.html
правда дальше я не думал, но вроде как подходит, есть asCharBuffer и проч.

Автор: Zandr 28.4.2005, 09:39
Если регекспы, то обрати внимание, что они ищут не в String'ах или StringBuffer'ах, а в CharSequence. CharSequece это интерфейс. Можешь попробовать реализовать что-нить типа:
Код

public class BigDocument implements CharSequence
{
// а здесь используешь, например, RandomAccessFile 
// в качестве поставщика данных
}

На счет быстродействия ничего сказать не могу - как реализуешь.

Автор: Stampede 28.4.2005, 10:32
Котъ, Zandr - спасибо за идеи. Будет чем заняться завтра на работе smile

Автор: Stampede 29.4.2005, 01:04
Докладаю.

Сижу и тащусь как удав по пачке дуста. Работает - проооосто как песТня. Всего делов было - несколько строк преобразований типа RandomAccessFile -> FileChannel -> MappedByteBuffer -> (Charset, CharsetDecoder) -> CharBuffer.

Фсе! Далее абстрагируемся от подлежащего (underlying) файла и регекспим как обычную строку!

Спасибо, отцы! Да здравствует Винградовка!

smile

ЗЫ. Хотел не радостях вам плюсов надавать - не пущает. Маладой, говорит, ишшо. Это я-то молодой? Хе-хе ... Котъ, ну хоть Zandr'у влепи одного - от имени, тыкскыть, и по поручению благодарной общественности smile

Автор: Domestic Cat 29.4.2005, 01:06
Ура так сказать! Тащимся вместе с тобой ! smile smile smile

Автор: Zandr 29.4.2005, 06:43
Цитата
FileChannel -> MappedByteBuffer -> (Charset, CharsetDecoder) -> CharBuffer

Красиво smile

Автор: Stampede 29.4.2005, 19:54
Любознательным на заметку, дополнение:

Цитата(Zandr @ 29.4.2005, 06:43)
FileChannel -> MappedByteBuffer -> (Charset, CharsetDecoder) -> CharBuffer

Красиво


Красиво-то оно может и красиво, но я вот сейчас попробовал получить буфер напрямую, без преобразований через CharDecoder - через метод asCharBuffer(), который предлагал Котъ, так оно теперь в 5 (пять!) раз быстрее мослает.

Но, блин, нет в мире совершенства. Это работает только с текстами в ASCII. Если нужна другая кодировка, придется все равно использовать CharDecoder. А что делать?

Да, кстати, с седьмым плюсом тебя? smile (tnx, AntonSaburov)


Автор: Stampede 30.4.2005, 00:43
Блин, погорячился. Вношу поправку.

Короче, по производительности никакого выигрыша почти нет. Просто я не заметил, что в варианте с asCharBuffer() содержимое интерпретировалось неправильно, поэтому регекспы ничего не находили, и в результате получалось быстрее. Нам, однако, такой хоккей не нужен. Нам нужно не быстро, а нам нужно, чтобы правильно.

Так вот, в ходе экспериментов выяснилось, что по дефолту тот буфер, который возвращает asCharBuffer() (какой-то внутренний класс DirectByteBuffer), поддерживает только кодировку UTF-16. Скорость обработки файла при этом сопостовима со скоростью в варианте с CharsetDecoder (правда, там сам исходный файл в UTF-16 в два раза длиннее, так что вроде бы работает быстрее, но нам ведь нужно ехать, а не шашечки smile).

Поиски способа сменить дефолтную кодировку ни к чему не привели, так что, как говорится, не выеживайтесь, Иван Иваныч, слушайте вашу любимую песню "Валенки" smile В смысле, юзайте CharsetDecoder и не дрыгайтесь.

На случай, если у кого-то возникнет аналогичная задача, привожу код манипуляций:

Код


    File file = new File("your-file-location");

    RandomAccessFile raf = new RandomAccessFile(file, "r"); // r, wr, p, etc.
    FileChannel fc = raf.getChannel();

    // Get the file's size and then map it into memory
    MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);

    // Setup the decoder
    Charset charset = Charset.forName("ISO-8859-1");
    CharsetDecoder decoder = charset.newDecoder();
    CharBuffer buffer = decoder.decode(bb);

    // Делаем что хотим с buffer, например:
    Pattern p = new Pattern("\\n");
    Matcher m = p.matcher(buffer);

    if (m.find())
    {
        System.out.println("LF found");
    }

    raf.close();


Автор: Zandr 4.5.2005, 07:05
Вот не совсем понятное место:
Цитата
CharBuffer buffer = decoder.decode(bb);

Не прошарил как работает этот метод... Он не пытается сразу весь файл перекачать в память? Или как задумано дает char'ы что называется on demand?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)