Подтвердить что ты не робот

Мониторинг прогресса анализатора Java SAX

Я пишу синтаксический анализатор SAX в Java для анализа 2,5-Гбайт XML файла статей в википедии. Есть ли способ отслеживать ход разбора в Java?

4b9b3361

Ответ 1

Используйте javax.swing.ProgressMonitorInputStream.

Ответ 2

Благодаря предложению EJP ProgressMonitorInputStream, в конце я расширил FilterInputStream, так что ChangeListener можно использовать для контроля текущего места чтения в байтах.

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

Итак, упрощенная версия контролируемого потока:

/**
 * A class that monitors the read progress of an input stream.
 *
 * @author Hermia Yeung "Sheepy"
 * @since 2012-04-05 18:42
 */
public class MonitoredInputStream extends FilterInputStream {
   private volatile long mark = 0;
   private volatile long lastTriggeredLocation = 0;
   private volatile long location = 0;
   private final int threshold;
   private final List<ChangeListener> listeners = new ArrayList<>(4);


   /**
    * Creates a MonitoredInputStream over an underlying input stream.
    * @param in Underlying input stream, should be non-null because of no public setter
    * @param threshold Min. position change (in byte) to trigger change event.
    */
   public MonitoredInputStream(InputStream in, int threshold) {
      super(in);
      this.threshold = threshold;
   }

   /**
    * Creates a MonitoredInputStream over an underlying input stream.
    * Default threshold is 16KB, small threshold may impact performance impact on larger streams.
    * @param in Underlying input stream, should be non-null because of no public setter
    */
   public MonitoredInputStream(InputStream in) {
      super(in);
      this.threshold = 1024*16;
   }

   public void addChangeListener(ChangeListener l) { if (!listeners.contains(l)) listeners.add(l); }
   public void removeChangeListener(ChangeListener l) { listeners.remove(l); }
   public long getProgress() { return location; }

   protected void triggerChanged( final long location ) {
      if ( threshold > 0 && Math.abs( location-lastTriggeredLocation ) < threshold ) return;
      lastTriggeredLocation = location;
      if (listeners.size() <= 0) return;
      try {
         final ChangeEvent evt = new ChangeEvent(this);
         for (ChangeListener l : listeners) l.stateChanged(evt);
      } catch (ConcurrentModificationException e) {
         triggerChanged(location);  // List changed? Let re-try.
      }
   }


   @Override public int read() throws IOException {
      final int i = super.read();
      if ( i != -1 ) triggerChanged( location++ );
      return i;
   }

   @Override public int read(byte[] b, int off, int len) throws IOException {
      final int i = super.read(b, off, len);
      if ( i > 0 ) triggerChanged( location += i );
      return i;
   }

   @Override public long skip(long n) throws IOException {
      final long i = super.skip(n);
      if ( i > 0 ) triggerChanged( location += i );
      return i;
   }

   @Override public void mark(int readlimit) {
      super.mark(readlimit);
      mark = location;
   }

   @Override public void reset() throws IOException {
      super.reset();
      if ( location != mark ) triggerChanged( location = mark );
   }
}

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

Итак, здесь используется упрощенная выборка:

try (
   MonitoredInputStream mis = new MonitoredInputStream(new FileInputStream(file), 65536*4) 
) {

   // Setup max progress and listener to monitor read progress
   progressBar.setMaxProgress( (int) file.length() ); // Swing thread or before display please
   mis.addChangeListener( new ChangeListener() { @Override public void stateChanged(ChangeEvent e) {
      SwingUtilities.invokeLater( new Runnable() { @Override public void run() {
         progressBar.setProgress( (int) mis.getProgress() ); // Promise me you WILL use MVC instead of this anonymous class mess! 
      }});
   }});
   // Start parsing. Listener would call Swing event thread to do the update.
   SAXParserFactory.newInstance().newSAXParser().parse(mis, this);

} catch ( IOException | ParserConfigurationException | SAXException e) {

   e.printStackTrace();

} finally {

   progressBar.setVisible(false); // Again please call this in swing event thread

}

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

Надеюсь, это поможет. Не стесняйтесь редактировать, если вы обнаружили ошибки или опечатки, или проголосуйте, чтобы прислать мне некоторые поощрения!: D

Ответ 3

Вы можете получить оценку текущей строки/столбца в вашем файле, переопределив метод setDocumentLocator org.xml.sax.helpers.DefaultHandler/BaseHandler. Этот метод вызывается с объектом, из которого вы можете получить приближение текущей строки/столбца при необходимости.

Изменить: Насколько мне известно, стандартного способа получить абсолютную позицию нет. Тем не менее, я уверен, что некоторые реализации SAX предлагают такую ​​информацию.

Ответ 4

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

public void startElement (String uri, String localName, 
                          String qName, Attributes attributes) 
                          throws SAXException {
    if(qName.equals("article")){
        counter++
    }
    ...
}

(Я не знаю, анализируете ли вы "статью", это просто пример)

Если вы не знаете количество статей заранее, вам нужно будет сначала его подсчитать. Затем вы можете распечатать статус nb tags read/total nb of tags, скажем, каждые 100 тегов (counter % 100 == 0).

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

Мои 2 цента

Ответ 5

Я бы использовал позицию входного потока. Создайте свой собственный тривиальный класс потока, который делегирует/наследует от "реального" и отслеживает чтение байтов. Как вы говорите, получить общий размер файла легко. Я бы не стал беспокоиться о буферизации, поиске и т.д. - для таких больших файлов, как это, это курица. С другой стороны, я бы ограничил позицию "99%".