現在、SAXパーサーを使用しようとしていますが、ファイル全体で約3/4が完全にフリーズします。より多くのメモリを割り当てようとしましたが、改善されていません。
これをスピードアップする方法はありますか?より良い方法ですか?
それを裸の骨に落としたので、今私は次のコードを持っています、そしてコマンドラインで実行するとき、それはまだ私が望むほど速く行きません。
「Java -Xms-4096m -Xmx8192m -jar reader.jar」を使用して実行すると、記事700000あたりのGCオーバーヘッド制限を超えます。
メイン:
public class Read {
public static void main(String[] args) {
pages = XMLManager.getPages();
}
}
XMLManager
public class XMLManager {
public static ArrayList<Page> getPages() {
ArrayList<Page> pages = null;
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("..\\enwiki-20140811-pages-articles.xml");
PageHandler pageHandler = new PageHandler();
parser.parse(file, pageHandler);
pages = pageHandler.getPages();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pages;
}
}
PageHandler
public class PageHandler extends DefaultHandler{
private ArrayList<Page> pages = new ArrayList<>();
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(){
super();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
stringBuilder = new StringBuilder();
if (qName.equals("page")){
page = new Page();
idSet = false;
} else if (qName.equals("redirect")){
if (page != null){
page.setRedirecting(true);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (page != null && !page.isRedirecting()){
if (qName.equals("title")){
page.setTitle(stringBuilder.toString());
} else if (qName.equals("id")){
if (!idSet){
page.setId(Integer.parseInt(stringBuilder.toString()));
idSet = true;
}
} else if (qName.equals("text")){
String articleText = stringBuilder.toString();
articleText = articleText.replaceAll("(?s)<ref(.+?)</ref>", " "); //remove references
articleText = articleText.replaceAll("(?s)\\{\\{(.+?)\\}\\}", " "); //remove links underneath headings
articleText = articleText.replaceAll("(?s)==See also==.+", " "); //remove everything after see also
articleText = articleText.replaceAll("\\|", " "); //Separate multiple links
articleText = articleText.replaceAll("\\n", " "); //remove new lines
articleText = articleText.replaceAll("[^a-zA-Z0-9- \\s]", " "); //remove all non alphanumeric except dashes and spaces
articleText = articleText.trim().replaceAll(" +", " "); //convert all multiple spaces to 1 space
Pattern pattern = Pattern.compile("([\\S]+\\s*){1,75}"); //get first 75 words of text
Matcher matcher = pattern.matcher(articleText);
matcher.find();
try {
page.setSummaryText(matcher.group());
} catch (IllegalStateException se){
page.setSummaryText("None");
}
page.setText(articleText);
} else if (qName.equals("page")){
pages.add(page);
page = null;
}
} else {
page = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
stringBuilder.append(ch,start, length);
}
public ArrayList<Page> getPages() {
return pages;
}
}
解析コードは正常に機能している可能性がありますが、ロードするデータの量が多すぎてそのArrayList
のメモリに保持できない可能性があります。
一度にすべてのデータをメモリに保存せずに、実際の宛先にデータを渡すためには、何らかのパイプラインが必要です。
この種の状況で時々行ったことは、次のようなものです。
単一の要素を処理するためのインターフェイスを作成します。
public interface PageProcessor {
void process(Page page);
}
コンストラクターを介してこの実装をPageHandler
に提供します。
public class Read {
public static void main(String[] args) {
XMLManager.load(new PageProcessor() {
@Override
public void process(Page page) {
// Obviously you want to do something other than just printing,
// but I don't know what that is...
System.out.println(page);
}
}) ;
}
}
public class XMLManager {
public static void load(PageProcessor processor) {
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("pages-articles.xml");
PageHandler pageHandler = new PageHandler(processor);
parser.parse(file, pageHandler);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
リストに入れるのではなく、このプロセッサにデータを送信します。
public class PageHandler extends DefaultHandler {
private final PageProcessor processor;
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(PageProcessor processor) {
this.processor = processor;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//Unchanged from your implementation
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//Unchanged from your implementation
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Elide code not needing change
} else if (qName.equals("page")){
processor.process(page);
page = null;
}
} else {
page = null;
}
}
}
もちろん、インターフェイスに1つだけでなく複数のレコードのチャンクを処理させ、PageHandler
で小さなリストでページをローカルに収集し、リストを定期的に送信して処理し、リストをクリアすることができます。
または(おそらくより良い)ここで定義されているPageProcessor
インターフェースを実装し、そこでデータをバッファリングし、チャンクでさらに処理するために送信するロジックをそこに構築できます。
Don Robyのアプローチは、この特定の問題を解決するために設計されたコードジェネレーターを作成したときのアプローチに似ています(初期バージョンは2008年に考案されました)。基本的に、各complexType
にはJava POJO
と同等のものがあり、コンテキストがその要素に変更されると、特定のタイプのハンドラーがアクティブになります。このアプローチは、SEPA、トランザクションバンキング、たとえばディスコ(30GB)に使用しました。プロパティファイルを宣言的に使用して、実行時に処理する要素を指定できます。
XML2JはcomplexTypes
をJava POJOにマッピングする一方で、リスンしたいイベントを指定できます。たとえば、.
account/@process = true
account/accounts/@process = true
account/accounts/@detach = true
本質は3行目にあります。デタッチにより、個々のアカウントがアカウントリストに追加されないようにします。したがって、オーバーフローしません。
class AccountType {
private List<AccountType> accounts = new ArrayList<>();
public void addAccount(AccountType tAccount) {
accounts.add(tAccount);
}
// etc.
};
コードでは、プロセスメソッドを実装する必要があります(デフォルトでは、コードジェネレーターは空のメソッドを生成します。
class AccountsProcessor implements MessageProcessor {
static private Logger logger = LoggerFactory.getLogger(AccountsProcessor.class);
// assuming Spring data persistency here
final String path = new ClassPathResource("spring-config.xml").getPath();
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);
AccountsTypeRepo repo = context.getBean(AccountsTypeRepo.class);
@Override
public void process(XMLEvent evt, ComplexDataType data)
throws ProcessorException {
if (evt == XMLEvent.END) {
if( data instanceof AccountType) {
process((AccountType)data);
}
}
}
private void process(AccountType data) {
if (logger.isInfoEnabled()) {
// do some logging
}
repo.save(data);
}
}
XMLEvent.END
は要素の終了タグをマークすることに注意してください。したがって、処理しているときは完了です。 (FKを使用して)データベースの親オブジェクトに関連付ける必要がある場合は、親のXMLEvent.BEGIN
を処理し、データベースにプレースホルダーを作成し、そのキーを使用して各子に格納できます。最後のXMLEvent.END
で、親を更新します。
コードジェネレーターが必要なものをすべて生成することに注意してください。そのメソッドと、もちろんDBグルーコードを実装するだけです。
始めるためのサンプルがあります。コードジェネレーターはPOMファイルも生成するため、生成後すぐにプロジェクトをビルドできます。
デフォルトの処理方法は次のとおりです。
@Override
public void process(XMLEvent evt, ComplexDataType data)
throws ProcessorException {
/*
* TODO Auto-generated method stub implement your own handling here.
* Use the runtime configuration file to determine which events are to be sent to the processor.
*/
if (evt == XMLEvent.END) {
data.print( ConsoleWriter.out );
}
}
ダウンロード:
最初にmvn clean install
コア(ローカルのMavenリポジトリにある必要があります)、次にジェネレーター。また、ユーザーマニュアルの指示に従って、環境変数XML2J_HOME
を設定することを忘れないでください。