- PR -

XMLファイルへの要素の追加について

投稿者投稿内容
uniqque
常連さん
会議室デビュー日: 2005/03/18
投稿数: 43
投稿日時: 2005-07-16 16:59
こんにちは。
XMLファイルへ要素を追加するプログラムを書いているのですが、
とても非効率なコードになってしまいました。
以下がコードです。
**************************************************
public void xmlValue_append(String path,String value1){
String[] v1=new String[300];

int listLength=0;

try {
DocumentBuilderFactory dbfactory;
DocumentBuilder builder;
Document doc;
Element root;
NodeList list;
DOMImplementation domImpl;

dbfactory = DocumentBuilderFactory.newInstance();
builder = dbfactory.newDocumentBuilder();
doc = builder.parse(new File(path));

root = doc.getDocumentElement();
list = root.getElementsByTagName("tag");
listLength=list.getLength();

for(int i=0;i<listLength;i++){
Element element = (Element)list.item(i);

NodeList va1List = element.getElementsByTagName("va1");
Element va1Element = (Element)va1List.item(0);
v1[i]=va1Element.getFirstChild().getNodeValue();


}
Element tagElement;
Element va1Element;

TransformerFactory transFactory;
Transformer transformer;
DOMSource source;

dbfactory = DocumentBuilderFactory.newInstance();
builder = dbfactory.newDocumentBuilder();
domImpl=builder.getDOMImplementation();
doc = domImpl.createDocument("","root",null);
root=doc.getDocumentElement();

for(int i=0;i<listLength;i++){
tagElement= doc.createElement("tag");
va1Element=doc.createElement("va1");
va1Element.appendChild(doc.createTextNode(v1[i]));

tagElement.appendChild(va1Element);

root.appendChild(tagElement);
}
tagElement= doc.createElement("tag");
va1Element=doc.createElement("va1");
va1Element.appendChild(doc.createTextNode(value1));

tagElement.appendChild(va1Element);

root.appendChild(tagElement);

transFactory = TransformerFactory.newInstance();
transformer = transFactory.newTransformer();
source = new DOMSource(doc);
File newXML = new File(path);

FileOutputStream os = new FileOutputStream(newXML);
StreamResult result = new StreamResult(os);
transformer.transform(source, result);

}catch(Exception e){
e.printStackTrace();
}

}
**************************************************

一度要素を全部読み込んでから
それに追加して書き出すような処理をしてます。

もっといい方法はないかと色々と調べましたが
JAXBなどしかありませんでした。
JAXBなどを使わずにXMLファイルを更新したいのですが
他にいい方法がわかりません。
どなたか教えていただけませんか。
ご返答お待ちしてます。
スフレ
ぬし
会議室デビュー日: 2005/05/27
投稿数: 281
お住まい・勤務地: 東京
投稿日時: 2005-07-16 20:39
さほど悪くないコードだと思います。

改善するとしたら、Document.importNode() を使えば少しは楽になるかなあ、というのと、DocumentBuilderFactoryとDocumentBuilderは使い回せること、ぐらいでしょうか。
Anthyhime
ぬし
会議室デビュー日: 2002/09/10
投稿数: 437
投稿日時: 2005-07-16 20:51
まぁあとはXSLTでやるかですかね。
Gio
ぬし
会議室デビュー日: 2003/11/28
投稿数: 350
お住まい・勤務地: 都内から横浜の間に少量発生中
投稿日時: 2005-07-16 21:56
私も、処理ブロックごとに適切なコメントが入っていれたより良いと思いましたが、コメントなしでも何をやっているかきちんと読み取れるという点で悪くないと思いました。

スフレさんの書かれたことに補足&追加しますと、

- Document 下のノードのコピーは Document.importNode() を使うと二つの for ループが不要になる。
- 深い階層にあるノードを抽出する時は、XPath を使うともう少し見通しが良くなる。
パス自体が変わる可能性があるならば、ノード抽出はベタ書きでなく XPath による方が保守性が良い。
(今回のケースは浅いので、それほど気にしなくて良いです。importNode() を使うと tag/va1 要素の抽出自体不要になります。)
- 1 メソッド中でファイルパーズ、ノード追加、ファイル書き出しを全部ベタ書きしているので、パーズと書き出しくらいは別メソッドにして分離するとこのメソッドで実行する処理の本質が見やすくなる。

あたりでしょうか。

細かい点を挙げると、ファイル書き出しが終わった後でストリーム os をきちんとクローズするのもお忘れなく
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2005-07-16 23:17
例えば、AシステムとBシステムがあって、物理的に別システムだとします。
別のマシンや別のJVMなどのシステム等・・・

・Aシステム(物理XMLファイルを作成)
・Bシステム(物理XMLファイルを参照)

こういう構成ならuniqqueさんのソースも悪くないと思います。
皆さん指摘されているような部分はもっともですが。
ただ、追加対象となるノードが決まっているのであれば
SAXっていう手段もいいでしょう。
SAXでパースしながらファイルに出力するってことも可能です。
パース中に追加対象ノードであれば、
追加になるデータも書き出す事によって、現在と同じ事ができると思います。
ちなみに、DOMもSAXによって構築されています。

逆に同一のシステムであれば、さらに改善の余地があると思います。
追加要求時には、キャッシュされたDOMがあればそれを使用し、
キャッシュに存在しなければ新たにDOMを作成してキャッシュします。
物理ファイル要求時に始めてそのDOMを書き出すようにすれば
レスポンスがもっと向上するでしょう。

ただし、キャッシュが膨大になればメモリが足りないという
悲惨な状態になるかもしれませんのでキャッシュの解放が必要になりますが、
SoftReferenceやWeakReferenceを使用すれば、
DOMのキャッシュに起因するOutOfMemoryErrorは防げます。
SunのJDKを使用しているのであれば、
sun.misc.SoftCacheクラスがSoftReferenceによるキャッシュを実現しています。

参考までに。
uniqque
常連さん
会議室デビュー日: 2005/03/18
投稿数: 43
投稿日時: 2005-07-16 23:53
たくさんのご返答ありがとうございます。
非効率なコードというのは、処理的にとても非効率だったからです。
以前にこのようなXMLファイルから読み出したり、
XMLファイルへ書き出すというアプリケーションを作成したのですが、
追加する要素数が数十個なら普通通りなのですが、
これが数百にまで膨れると処理にとても時間がかかり、
数秒またなければいけませんでした。
1000個を越えるとフリーズしたかのような動きになっていました。
なのでできるだけ軽快に動作させたいと思って投稿しました。
ですが最初に書いたコードのような簡単な文法しかまだ理解できず、
JAXBやJAXPなどよさそうなのはあるのですが
CLASSPATHを設定したり、インストール?をしたり
そして実装も結構難しそうで挫折してしまったんです。

>>スフレさん
>DocumentBuilderFactoryとDocumentBuilderは使い回せること、ぐらいでしょうか。
ということは最初からDocumentBuilderのみでいけるということなのでしょうか。

>>Anthyhimeさん
XSLTってhtmlに変換するのですよね。
xsltで要素を取り出すとかできるのでしょうか。

>>Gioさん
Document.impoteNode()を使ってみましたが
引数にNodeを指定するというのと戻り値がNodeみたいで、
どのように実装すればよいのかわかりませんでした・・・。


Element root = doc.getDocumentElement();
Node rootnode=doc.importNode(root.cloneNode(true),true);
NodeList r=rootnode.getChildNodes();
root = (Element)r.item(0);

このように実装してみたのですが、
追加がうまくいかないみたいでした。
もしよろしければ簡単な実装方法など教えていただけないでしょうか。

>>かつのりさん
このアプリケーションはフリーソフトみたいな感じで作ってるので
別システムで使うことはないです。
追加対象となるノードが決まってるのはrootの親ノードのみで、
プログラムのほうで色々な子ノードを入れるようなアプリケーションなんです。
DOMを使う限りメモリ増大は避けられないんですね。

かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2005-07-17 00:46
適切なチューニングというのは、ある程度使用方法を想定した上で行います。
どんな条件下でも動くものになれば、それなりの制限が必要になります。
例えば速度面での妥協などの、妥協点を探す必要があります。

今回妥協できないのはパフォーマンス面ですよね。
ノードの追加のたびに物理的なIOが増えれば遅くなってしまうのは仕方がありません。
さらにDOMの構築もそれなりのコストがかかります。
逆にDOMの操作を少々効率化したからといっても効果は微々たるものです。
効率化を図るには、ボトルネックを探すのもいいと思います。

ちなみに、JAXPはXMLパーサやDOMを作成する部分の仕様にあたります。
貴方のコードでは、もうすでに使用していますね。
JAXBはJavaとXMLのバインディング技術です。
XMLとJavaのオブジェクトと結びつける技術であり、
今回のケースとは全然関連がありません。

また、DOMはメモリを食いますが問題なのはキャッシュを行った場合です。
どうしてもメモリがやばい状態での処理なら、
毎回読み込み・書き込みを行う必要があると思います。
それも妥協点になるでしょう。
要件次第だと思います。

[ メッセージ編集済み 編集者: かつのり 編集日時 2005-07-17 00:50 ]
スフレ
ぬし
会議室デビュー日: 2005/05/27
投稿数: 281
お住まい・勤務地: 東京
投稿日時: 2005-07-17 01:01
なるほど、実行時のパフォーマンスを気にしているんですね。

であれば、DOMを使うと遅いのはどうしようもないので、かつのりさんが書いてるように、SAXで何とかするのがいいでしょう。org.xml.sax.helpers.XMLFilterImplのサブクラスでメインの処理(tag/va1を残してrootを付けるような)をして、SAXSourceとStreamResultでシリアライズすれば良さそうです。




[ メッセージ編集済み 編集者: スフレ 編集日時 2005-07-17 01:02 ]

スキルアップ/キャリアアップ(JOB@IT)