我正在用Java编写一个SAX解析器来解析维基百科文章的2.5GB XML文件。有没有办法监视Java中的解析进度?
答案 0 :(得分:11)
感谢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<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'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
}
在我的情况下,进展从左到右很好地提升而没有异常跳跃。调整阈值以实现性能和响应性之间的最佳平衡。太小,读取速度在小型设备上可能会翻倍,太大,进度也不会很顺利。
希望它有所帮助。如果您发现错误或拼写错误,请随时编辑,或投票给我一些鼓励! :d
答案 1 :(得分:9)
使用javax.swing.ProgressMonitorInputStream.
答案 2 :(得分:2)
您可以通过覆盖setDocumentLocator
的方法org.xml.sax.helpers.DefaultHandler/BaseHandler
来估算文件中的当前行/列。使用一个对象调用此方法,您可以在需要时获得当前行/列的近似值。
编辑:据我所知,没有标准的方法来获得绝对位置。但是,我确信一些SAX实现确实提供了这种信息。
答案 3 :(得分:1)
假设你知道你有多少文章,你不能只在处理程序中保留一个计数器吗? E.g。
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美分
答案 4 :(得分:0)
我会使用输入流位置。创建自己的普通流类,它从“真实”代理/继承,并跟踪读取的字节。正如您所说,获取总文件大小很容易。我不担心缓冲,前瞻等等 - 对于像这样的大型文件它是鸡饲料。另一方面,我将头寸限制在“99%”。