![]() |
Модераторы: LSD, AntonSaburov |
![]() ![]() ![]() |
|
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 24 Всего: 144 |
Эта тема - попытка рассуждать вслух, в надежде разработать по возможности максимально эффективный компонент, который пригодится и другим участникам.
Если кто-то помнит, некоторое время назад на форуме обсуждался вопрос применения регулярных выражений к содержимому файла в потоковом режиме, то есть без предварительного считывания всего файла в память: http://forum.vingrad.ru/index.php?showtopic=50114&st=0. В ходе обсуждения было найдено решение, использующее MappedByteBuffer и основанное на факте, что методы поиска регекспов принимают в качестве аргументов не String, а интерфейс CharSequence. Все было в общем-то хорошо, за исключением одного момента: при таком решении файл таки целиком закачивался в память. До поры до времени возникающие проблемы нехватки памяти удавалось решить увеличением размера кучи, но недавно попался такой здоровенный файл, что захотелось решить проблему раз и навсегда - посредством написания класса, который бы полностью не зависел от размера исходного файла. Итак, задача: Требуется класс, который реализует интерфейс CharSequence:
Класс должен открывать указанный текстовый файл и дольше вести себя как CharSequence, скрывая детали того, откуда берутся данные. Если бы имелось взаимно однозначное соответствие между позицией символа в строке и смещением байта (байтов) в файле, то реализация получилась бы достаточно тривиальной: позиционирование внутри файла, буферное чтение, декодирование на лету и пр. Прикол в том, что в общем случае для наперед неизвестной кодировки такое не прокатит, потому что при произвольном чтении ты не знаешь, попадает ли у тебя указатель на начало или середину юникодного символа. Поэтому в качестве первого варианта попробую такой подход: 1. При открытии файла выполняется его полный побуферный проход с заполнением следующей информации: номер, смещение в файле, длина в байтах, позиция в строке, длина в символах. 2. Полученный список описателей буферов позволит по индексу символа быстро вычислить номер буфера и его смещение в файле, а внутр него - позицию искомого символа в строке (при условии, что с буфером будет связана соответствующая его содержимому строка). Для ускорения работы компонента несколько последних буферов можно кэшировать в памяти, скажем, в виде массива со стратегией замещения "наиболее долго не использовавшийся - вылетает". Но это уже детали. Сейчас сажусь кодировать, но буду также по ходу прислушиваться к мнениям товарищей. За любые соображения, идеи, замечания - буду весьма признателен. Пишите ![]() Это сообщение отредактировал(а) Stampede - 17.8.2005, 00:00 -------------------- "If you want something done right, do it yourself" По секрету: выучить английский - реально! |
|||
|
||||
tux |
|
||||
![]() Летатель ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1853 Регистрация: 10.2.2005 Где: msk.ru Репутация: 31 Всего: 132 |
Решил предложить два варианта решения проблемы с поиском в файлах. Оба варианта далеко не универсальны и в данном случае вряд ли подойдут и больно уж простые чтобы никому не приходили в голову
![]() Вариант 1. Если поиск выполняется в простом текстовом файле с переносами строк и весь текст файла представлен в одной кодировке (то есть в файле ASCII нет полей, заколдированных, например, в UNICODE), то подойдет такое решение:
Анализируемый файл может быть любого размера, вся память, которая используется для чтения - это буфер заданной (или дефолтной длины). Вариант 2. Если файл без переносов строк, но выполняется поиск простых выражений и, опять-таки, весь файл - в одном кодировке, то можно обойтись токенайзером следующим образом:
Используется тот же самый класс java.io.BufferedReader. Анализируя соседние токены можно находить относительно сложные выражения. Хотя скорость работы в этом случае будет скорее всего значительно ниже, да и ограничений гораздо больше. |
||||
|
|||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 24 Всего: 144 |
tux, спасибо за предложения, но оба варианта предполагают строго последовательное чтение, а по условию моей задачи требуется обеспечить произвольный доступ (да, признаю, я этого в явном виде не сформулировал). То есть там алгоритм таков, что один регексп находит начало и конец отдельного документа, а потом другие регекспы ищут внутри него значения всяких полей.
Завтра напишу, что получается. |
|||
|
||||
tux |
|
|||
![]() Летатель ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1853 Регистрация: 10.2.2005 Где: msk.ru Репутация: 31 Всего: 132 |
Если найти строку с началом документа, а потом анализировать строки насчет наличия значений полей до тех пор пока не встретится конец документа, наверное можно все сделать в один проход. Хотя надо конечно видеть формат файла, так сложно что-то сказать.
|
|||
|
||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 24 Всего: 144 |
Отчитуюсь
![]() Компонент написан, мослает как по маслу ![]() В общем, примерно как все задумывалось (см. первый пост), так и получилось. Но не обошлось и без трюков. Прежде всего, наткнулся на классическую проблему "рисовальщика дорожной разметки", или "чем дальше в лес, тем толще партизаны". То есть по мере углубления в файл на поиск соответствующего описателя буфера уходило все больше и больше времени (количество описателей измерялось тысячами). Пришлось для их хранения вместо List'а приспособить TreeSet. Далее, возник вопрос: что возвращать в качестве subSequence? Дело в том, что у меня в результате парсинга получалась приличная структура со вложенными дескрипторами участков текста, и к каждому из них нужно было применять всякие регекспы. Поэтому вызов charAt(int index) самого внутреннего дескриптора должен был по идее отфутболивать запрос вверх по иерархии (с откорректированным индексом на величину смещения позиции самого дескриптора), и так до тех пор, пока он не добрался бы до моего основного класса, который уже мог бы полезть в соответствующий буфер и считать нужный символ. Поэтому пришлось написать вспомогательный класс, реализующий CharSequence, который бы служил окошком во внутренности содержащего объекта типа CharSequence. Самым корневым родителем в этом случае, естественно, оказывался объект моего класса. Кэш буферов я сделал в виде простого списка заданного размера. Каждый раз при обращении к charAt() находится нужный описатель буфера. Если соответствующий ему собственно буфер обнаруживается в кэше, он передвигается в голову списка. Если нет, буфер считывается с диска и также помещается в голову. Хвостовой элемент при этом удаляется. Кстати, мне уже не первый раз приходится пользоваться подобной штукой, так что даже непонятно, почему ее нет в составе Collections (или я что-то пропустил?). И последняя вешь, которую я хотел бы упомянуть, это проблема с методом toString(). Если бы я в своем классе subSequence реализовал ее как parent.toString().substring(start, end), то для получения даже самой малюсенькой строчечки во внутреннем Subsequence пришлось бы по цепочке инстанциировать все строки, принадлежащие предкам, и это было бы очень накладно. Поэтому я сделал так. чтобы все промежуточные сиквенсы реализовывали дополнительный метод, String getString(int start, int end). то есть просто отфутболивали его наверх со смещением. И тогда основной класс уже считывал строку ровно такой длины, которая требовалась в исходном вызове. Для этого пришлось ввести дополнительный интерфейс, наследующий от CharSequence, и кое-что маленько срефакторить, но это, конечно, мелочи. А главный итог - это что программа работает, да так, что уши закладывает. Сильно экспериментами не заморачивался, но на ряде тестовых прогонов получил такие результаты: При кэше в 4 буфера по 64 k обработка файла длиной 10 мег занимает 40 секунд. Тот же файл в прежней версии (весь файл в памяти) обрабатывался 35 секунд. Расход памяти - 16 и 42 мега соответственно. Чистый итог: получилось дешево и кошерно ![]() -------------------- "If you want something done right, do it yourself" По секрету: выучить английский - реально! |
|||
|
||||
LSD |
|
|||
![]() Leprechaun Software Developer ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 15718 Регистрация: 24.3.2004 Где: Dublin Репутация: 210 Всего: 538 |
В принципе эту проблему можно решить используя CharsetDecoder.averageCharsPerByte(), но для некоторых кодировок (тот же UTF-8) это число дробное. -------------------- Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it. |
|||
|
||||
Zandr |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 433 Регистрация: 16.7.2004 Где: Новосибирск Репутация: 9 Всего: 13 |
Вот и строители наши тоже дома строят с точностью плюс-минус пол кирпича. |
|||
|
||||
LSD |
|
|||
![]() Leprechaun Software Developer ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 15718 Регистрация: 24.3.2004 Где: Dublin Репутация: 210 Всего: 538 |
При чем тут это? Если в Charset используется постоянное количество байт на символов, то проще и быстрее будет вычислять смещение в файле. -------------------- Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it. |
|||
|
||||
Stampede |
|
|||
![]() Гносеолог ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 963 Регистрация: 25.4.2005 Где: Calgary, Alberta, Canada Репутация: 24 Всего: 144 |
Кстати, отличная мысль.
Например, в моем случае все тексты - в банальном ASCII. Если принять это за данность, то код получится действительно совершенно тривиальным. В частности, отпадает необходимости в предварительном сканировании и хранении описателей (которые могут занимать прилично памяти в случае длинного файла). Другое дело, что я маленько параноик в части универсальности разрабатываемых компонентов, что и повлияло на мой подход к реализации. Но ведь можно же сделать фабрику! Тогда для простых кодировок будет инстанциироваться простая реализация, а для всяких китайских - сложная. И все детали скрыты! Ура! LSD, держи в репу ![]() |
|||
|
||||
![]() ![]() ![]() |
Правила форума "Java" | |
|
Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Java: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |