Java SAX解析器进度监控

我正在用Java编写一个SAX解析器来解析维基百科文章的2.5GB XML文件。 有没有办法监视Java中的解析进度?

使用javax.swing.ProgressMonitorInputStream.

感谢EJP对ProgressMonitorInputStream的建议,最后我扩展了FilterInputStream以便可以使用ChangeListener以字节为单位监视当前读取位置。

有了这个,你可以更好地控制,例如显示多个进度条,用于并行读取大xml文件。 这正是我所做的。

所以,monitorable流的简化版本:

 /** * 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 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's 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 } 

在我的情况下,进展从左到右很好地提升而没有exception跳跃。 调整阈值以实现性能和响应性之间的最佳平衡。 太小,阅读速度可能会在小型设备上翻倍,太大,进度也不会平稳。

希望能帮助到你。 如果您发现错误或拼写错误,请随时编辑,或投票给我一些鼓励! :d

您可以通过覆盖org.xml.sax.helpers.DefaultHandler/BaseHandler的方法setDocumentLocator来估计文件中的当前行/列。 使用对象调用此方法,您可以在需要时从中获取当前行/列的近似值。

编辑:据我所知,没有标准的方法来获得绝对的位置。 但是,我确信一些SAX实现确实提供了这种信息。

假设你知道你有多少文章,你不能只在处理程序中保留一个计数器吗? 例如

 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美分

我会使用输入流位置。 创建自己的普通流类,它从“真实”代理/inheritance,并跟踪读取的字节。 正如您所说,获取总文件大小很容易。 我不担心缓冲,前瞻等等 – 对于像这样的大文件它是鸡饲料。 另一方面,我将头寸限制在“99%”。