web-dev-qa-db-ja.com

Javaで大きな(50 GB)XMLファイルを解析する方法

現在、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;
     }
}
23
Joe Maher

解析コードは正常に機能している可能性がありますが、ロードするデータの量が多すぎてその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インターフェースを実装し、そこでデータをバッファリングし、チャンクでさらに処理するために送信するロジックをそこに構築できます。

30
Don Roby

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を設定することを忘れないでください。

0
dexter