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


Автор: Sardar 7.2.2006, 13:17
Есть масса логов, пишуться java.util.logging через XMLFormatter. Получаю длиннющую (в реале и на пол гига, не шучу) простыню, которую нужно уметь быстро просматривать.

При старте нужно зачитать первую сотню записей, показать, дабы юзер не думал что прога тормозит. Отдельный тред пробегаеться по всему файлу, строит графическую полоску (удобно видеть где что в логе) и индексы. Как только индекс [номер записи : позиция в файле] доступно, можно спокойно скролить и прыгать в любое место.

Вопрос: как реализовать быстрые переходы по файлу, альтернатива reset/skip? Или есть у кого результаты тестов этих операций под разными платформами (интересует RHE Linux, Solaris)? Или просто совет кто уже делал подобное?

В итоге пишу Reader -> InputSource -> XMLReader.parse, так, что последний не должен заметить что прыгаем по файлу, как будто одна длинная простыня.

Название топу дал такое, т.к. вопрос больше как сабж. лучше сделать, может и не нужно вовсе свой Reader писать smile

Автор: LSD 7.2.2006, 15:40
Цитата(Sardar @ 7.2.2006, 13:17 Найти цитируемый пост)
Вопрос: как реализовать быстрые переходы по файлу, альтернатива reset/skip? Или есть у кого результаты тестов этих операций под разными платформами (интересует RHE Linux, Solaris)? Или просто совет кто уже делал подобное?

http://forum.vingrad.ru/index.php?showtopic=61598 был у Stampede, только там был не Reader а CharSequence. Доработка его кода понадобится, но хоть что-то.

Автор: Stampede 7.2.2006, 19:03
Цитата(LSD @ 7.2.2006, 15:40 Найти цитируемый пост)

Похожий вопрос был у Stampede, только там был не Reader а CharSequence.


Я тоже сперва об этом подумал, но потом прикинул, что оно скорее всего не подойдет: если регекспу пофиг с какого места парсить, то XML ридеру таки нужен контекст.

Другое дело, что можно при первом проходе SAX парсером отлавливать начало и конец уаждой отдельной записи и заносить эту инфу в таблицу. А потом по мере скроллинга восстанавливать по номеру записи ее смещение, считывать из RandomAccessFile полный текст соответствующего ей XML элемента, и тогда уже добирать всю остальную инфу, относящуюся к записи, путем парсинга этого элемента.

У меня кстати примерно на таком принципе построен вьюер тех здоровых файлов, про которые я писал в упомянутом топике. Причем не просто вьюер, а вьюер с подсветкой найденных элементов. И работает, кстати, весьма шустро. Так что есть смысл попробовать.

Автор: LSD 7.2.2006, 21:41
Стандартный XML парсер так работать не сможет конечно, так как не любую структуру можно так распарсить. Но тут структура XML очень простая, и это можно использовать.
В принципе можно написать свой Reader который бы позволил парсить файл с произвольной позиции (есть пара идей).

Sardar подкинь структуру XML, по возможности в виде схемы.

Автор: Sardar 7.2.2006, 22:25
Цитата(LSD @ 7.2.2006, 20:41 Найти цитируемый пост)
подкинь структуру XML

Пишеться через java.util.logging.XMLFormatter, http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html#2.4 и http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html#3.0

Цитата(Stampede @ 7.2.2006, 18:03 Найти цитируемый пост)
то XML ридеру таки нужен контекст

Для любого XML конечно нужен контекст, но логи это всё таки поток обьектов без навороченной структуры. Отсюда идея пока так:

[стрим из файла] -> {мой стрим/ридер с методом seek} -> [XMLReader] -> {логика} -> {анализатор}

//в фигурных моё, в квадратных классы из стандартной либы

Т.е. оборачиваю бычный стрим своим wrapper'ом, что (пока) методами reset/skip будет свободно перемещаться по стриму. Памяти кушать могут мало, потому хранить прочитанное долго не буду, стрим должен поддерживать reset(). Логика делает что нужно для задачи, одновременно посылает в анализатор уведомления [начался требуемый обьект, закончился требуемый обьект]. Таким образом должен создаваться индекс с позициями обьектов.

Если пользователь решит скролить назад, то позиции имеем, если вперёд, то нужно прочитать на перёд обновляя индекс. Идея не плохая, но что то не понял как узнать на каком символе сейчас находимся, SAX парсер не даёт контекстной информации... Интерфейс Locator есть, но всё натыкаюсь что "пользователь должен имплементировать сию функциональность". Может плохо искал...

Отсюда проблема: как узнать на каком символе сейчас находимся получая SAX события?

Если парсер ещё и буфферизует сам символы или события на перёд, то идее конец... придёться брать другой SAX парсер. Вроде апачи что то мощное имеют, все кто знаком с альтернативными парсерами откликнитесь smile

Автор: LSD 8.2.2006, 12:30
Цитата(Sardar @ 7.2.2006, 22:25 Найти цитируемый пост)
Памяти кушать могут мало, потому хранить прочитанное долго не буду, стрим должен поддерживать reset().

FileInputStream не поддерживает mark(), только BufferedInputStream который делает это все за счет кеша в памяти.

Я честно говоря не понял зачем тебе понадобилось знать на каком символе сейчас находимся получая SAX события, что это тебе даст? Все равно парсер нельзя остановить или сказать ему парсить обратно.

Я бы сделал так: пишем свой java.io.Reader, которому указываем смещение и количество байт которое надо прочитать. Наш Reader читает файл начиная с этого смещения и ищет открывающий тег (<record>) как только находит выдает:
Код
<?xml version="1.0"?>
<log>
<record>
...

и далее все что прочтет пока не превысит лимит прочитанных байт, тогда ждет закрывающий тег (</record>) и в дополнение к нему выдает закрывающий </log>. При этом мы можем использовать любой парсер хоть SAX хоть DOM.

Автор: Sardar 8.2.2006, 13:03
Цитата(LSD @ 8.2.2006, 11:30 Найти цитируемый пост)
Все равно парсер нельзя остановить или сказать ему парсить обратно

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

Цитата(LSD @ 8.2.2006, 11:30 Найти цитируемый пост)
Я бы сделал так: пишем свой java.io.Reader

Ага, говорим об одном и том же, просто я задумался чуть дальше. Как сказать своему ридеру, что с текущей позиции начался новый обьект? Ридер должен тогда занести позицию в индекс [порядковый номер обьекта => смещение], далее прыгать можно сразу пользуя порядковый номер обьекта.

Проблемы:
  • если парсер читает на перёд, то ридер уебжит далеко вперёд, в это время получив очередной тег соображаем что здесь начало обьекта, пытаемся сказать об этом ридеру, а он уже чёрт знает где...
  • если получать позиции тегов в документе(offset) в символах, как все человеческие парсеры делают, то тоже можно двигаться, но возникнут следующие проблемы:
    • как заюзать Locator, в доке что то размытое, имплементации интерфейсу не нашёл...
    • требуеться прослойка что будет разбираться с UTF и подобными кодировками. Можно на манер Stampede, но позиции хранить регионом, т.е. для всего английского текста будет по сути одно вхождение. Эффективно, но вряд ли реализую что нибудь кроме UTF-8 и UTF-16, что плохо.
    • если парсер читает на перёд, то нужно как то очищать его буффер.

Млин, а начиналось как весьма простая задача... smile

Автор: LSD 11.2.2006, 13:37
Значита так:
С Locator я разобрался, там все просто как 3 копейки. Когда ты задаешь свой DefaultHandler устанавливает ему свой Locator (если конечно парсер поддерживает такую фишку). Парсер из JDK 1.5 поддерживает, юзать так:
Код
public class PrintStreamHandler extends DefaultHandler
{
  private PrintStream out;
  private PrintStream err;
  private Locator locator;

  public PrintStreamHandler(PrintStream out, PrintStream err)
  {
    this.out = out;
    this.err = err;
  }

  public void setDocumentLocator(Locator locator)
  {
    this.locator = locator;
  }

  private String getPosition()
  {
    if(locator == null)
      return "";
    return " at line: " + locator.getColumnNumber() + ", column: " + locator.getColumnNumber();
  }

  public void startDocument() throws SAXException
  {
    out.println("Document start" + getPosition());
  }

  public void endDocument() throws SAXException
  {
    out.print("Document end" + getPosition());
  }

  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
  {
    out.println("  element " + qName + " start" + getPosition());
  }

  public void endElement(String uri, String localName, String qName) throws SAXException
  {
    out.println("  element " + qName + " end" + getPosition());
  }

  public void characters(char ch[], int start, int length) throws SAXException
  {
    String str = new String(ch, start, length).trim();
    out.println("    data: '" + str + "'" + getPosition());
  }

  public void warning(SAXParseException e) throws SAXException
  {
    err.println("WARNING" + getPosition() + ": " + e);
  }

  public void error(SAXParseException e) throws SAXException
  {
    err.println("ERROR: " + e);
  }

  public void fatalError(SAXParseException e) throws SAXException
  {
    err.println("FATAL: " + e);
  }
}

Правда это мало помогает, т.к. там идут координаты в строка/символ, а для Reader надо в байтах.

Парсер читает столько данных сколько есть в потоке. Если скармливать ему по одному символу, то начало/конец тега получим как только он получит последний символ.


Теперь к Reader-у считать данные из произвольной позиции файла и отдать их не проблема, вопрос только в этих позициях. Можно хранить два массива long в одном номер записи, в другом смещение в байтах от начала файла. Но тут вылезает вопрос памяти, если считать, что файл имеет размер 1Гб, а размер одной записи 300 байт, то у нас будет около 3 500 000 записей и на это дело надо будет 58Мб. Так что придется хранить каждую 16 или даже 32 запись. Но это в принципе не проблема, проблема в том как заполнить этот индекс.

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