大きすぎてメモリに収まらないXMLファイルを処理しようとしています。それらのサイズは、数十メガバイトから120GB以上までさまざまです。私の最初の試みは、一度に数千文字のチャンクでファイルをプレーンテキストとして読み取り、小さなString
チャンクで個々の完成したXMLタグを探すことでした。
FileReader fileReader;
try {
fileReader = new FileReader(file);
DocumentBuilder factory = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc;
int charsToReadAtOnce = 1000;
char[] readingArray = new char[charsToReadAtOnce ];
int readOffset = 0;
StringBuilder buffer = new StringBuilder(charsToReadAtOnce * 3);
while(fileReader.read(readingArray, readOffset, charsToReadAtOnce ) != -1) {
buffer.append(new String(readingArray));
String current = buffer.toString();
doc = factory.parse(new InputSource(new StringReader(buffer.toString())));
//see if string contains a complete XML tag
//if so, save useful info and manually clear it
}
} catch (ALL THE EXCEPTIONS...
これは、1000文字を超えるタグのようなEdgeの多くのケースと、開始タグと終了タグを無視することで、乱雑で複雑なものになってしまいました。急ぐ代わりに、私はそれほど苦痛のないアルゴリズムを使いたいのですが、本当の良いアルゴリズムを思い付くことができません。 Javaには、このような大規模なXMLファイルを処理するためのより適切な方法がありますか?この質問をしているときに、私は偶然に遭遇しました 。NETで圧縮されたxmlを読んでください 。そのようですが、明らかにJavamightの場合は機能しますが、存在するかどうかわかりませんか?
ストリーミングAPI(SAXなど https://docs.Oracle.com/javase/tutorial/jaxp/sax/ を参照)とDOM APIの比較。前者は発生時に1つのプロセスタグを使用し、後者はメモリ内のDOMモデル全体を表します。参照 https://stackoverflow.com/q/6828703/7441
YoYoは、ほぼすべてのアプリケーションで機能する適切な回答を提供していますが、純粋なストリーミングパーサーよりも優れているものがあります。
たとえば、10億の銀行口座を持つファイルを考えてみます。
<accounts>
<account>
<id>1</id>
<balance>123.45</balance>
</account>
....
</accounts>
これで、このファイル全体をストリーミングパーサーで解析できます。ただし、問題は、ストリーミングパーサーの使用が面倒であり、ツリーパーサーの使用がはるかに優れていることです。残念ながら、メモリの制約により、ファイル全体を1つの大きなツリーで表すことはできません。
解決策は、「<account>」タグが表示されてツリーコレクションモードがオンになる場合と、「</ account>」タグが表示されている場合にツリーを完成させ、終了タグが検出されるたびに次のツリーが表示されるようにすることです。
<account>
<id>1</id>
<balance>123.45</balance>
</account>
その後、便利な方法でドキュメントのフラグメントにツリーとしてアクセスできますが、ドキュメント全体は1つの大きなツリーとしてメモリにありません。
このようなツリーコレクションコードをストリーミングパーサーの上で開発する必要があります。
この特定の問題を解決するように設計されたコードジェネレーターを作成しました(初期バージョンは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
を設定することを忘れないでください。