ウィキペディアの記事の 2.5 GB の XML ファイルを解析するために、Java で SAX パーサーを作成しています。Java で解析の進行状況を監視する方法はありますか?
5 に答える
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'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
}
私の場合、進行状況は異常なジャンプなしに左から右にうまく上昇します。パフォーマンスと応答性のバランスが最適になるようにしきい値を調整します。小さすぎると読み取り速度が2倍以上になる可能性があり、大きすぎると進行がスムーズになりません。
それが役に立てば幸い。間違いやタイプミスを見つけた場合は、自由に編集するか、投票して私にいくつかの励ましを送ってください!:D
使うjavax.swing.ProgressMonitorInputStream.
setDocumentLocator
のメソッドをオーバーライドすることで、ファイル内の現在の行/列の推定値を取得できますorg.xml.sax.helpers.DefaultHandler/BaseHandler
。このメソッドは、必要に応じて現在の行/列の概算を取得できるオブジェクトで呼び出されます。
編集:私の知る限り、絶対位置を取得する標準的な方法はありません。ただし、一部の 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セント
入力ストリームの位置を使用します。「実際の」ものから委任/継承し、読み取られたバイトを追跡する独自の単純なストリーム クラスを作成します。あなたが言うように、合計ファイルサイズを取得するのは簡単です。バッファリングや先読みなどについては心配しません。このような大きなファイルの場合は、チキンフィードです。一方、私はポジションを「99%」に制限します。