レベル: 中級 Michael Galpin (mike.sr@gmail.com), Developer, MichaelDKelly.com
2007年 7月 24日 XML パーサーがハイパフォーマンスで堅牢なアプリケーションの鍵となることは珍しくありません。従来からの XML の構文解析手法には DOM (Document Object Model) と SAX (Simple API for XML) などがありますが、今や革新的な新しい構文解析手法が登場しています。それが、Java EE (Java™ Platform, Enterprise Edition) 5 仕様に統合されるほど有益な StAX (Streaming API for XML) です。Java EE 5 の完全実装である Apache Geronimo 2.0 でも、StAX パーサーとして Codehaus の Woodstox を組み込んでいます。今回の記事では、StAX の利点、そして Geronimo チームが StAX パーサーとして Woodstox を選んだ理由を説明します。
XML の重要性
XML は 1996年、Tim Bray と Michael Sperberg-McQueen によって紹介されました。その可能性は広く認められたとはいえ、当時の人々にとって XML がこれほどまでに不可欠の技術になるとは想像に及びませんでした。今やエンタープライズ Java 開発者は XML を構成用やデータ・ストア、そして一般的にはデータ交換のフォーマットとして使用しています。XML は Web サービスと SOAP の基盤であり、それ故に現在のサービス指向アーキテクチャー (SOA) デザイン・パターンの基盤となっています。それだけではありません。Ajax (Asynchronous JavaScript + XML) の X にもなっているように、XML は最新の Web アプリケーションが実現する未だかつてないリッチなエクスペリエンスの重要な鍵でもあります。
ただし、XML は万能の解決策という訳ではなく、欠点もあります。1 つは、XML 文書はサイズが大きくなりだがちだという点です。また、XML 文書には一般のツリー構造がありますが、XML 文書に拡張性があるということはすなわち文書のスキーマが大幅に変化するということでもあります。このような欠点は、XML を効率的に構文解析する上で難問となります。そんな XML 構文解析の問題に対処するために今まで採られてきたのが、DOM と SAX という 2 つの手法です。
XML の処理: DOM と SAX
DOM と SAX は典型的な XML 構文解析手法です。この 2 つはいろいろな意味で、正反対の手法となっています。DOM は XML 文書に対して簡単なオブジェクト・モデルを提供します。DOM パーサーは XML 文書を使いやすいオブジェクトに変換し、このオブジェクトによって XML 文書のすべてのデータを表現します。しかし XML 文書をこのように忠実に表現するためには犠牲が伴います。それは、DOM による構文解析ではメモリーの使用量が大きくなりがちだということです。
SAX の場合、メモリーは問題になりません。SAX パーサーは一連の構文解析イベントを生成します。これらのイベントにコールバックを登録し、イベントからのデータにある種のロジックを実行するのはハンドラーの役目です。この手法は高速かつ効率的ですが、複雑なプログラミング・モデルが必要となります。
DOM を使用する場合と SAX を使用する場合の違いを理解し、それによって StAX を使用する理由と利点を理解するには、特定の例を見てみるのが一番手っ取り早い方法です。
Flickr を使用した構文解析例
構文解析する XML を見つけるのはわけありません。XML は至るところで使用されているからです。最近の Web サイトのほとんどは、ある種の XML ベースの Web サービスを提供しています。その一例が Flickr です。Flickr は Yahoo が所有する人気の高い写真共有サイトで、強力かつ柔軟な API を備えています。それではここで、Flickr の「面白い」写真にアクセスするための単純なコードを検討してみましょう (この記事で使用するソース・コースはすべて「ダウンロード」から入手できます。必ずクラスパスに Woodstox を含めるか、または JDK 1.6 を使用してください)。リスト 1 に、このコードを記載します。
リスト 1. Flickr API の使用
String apiKey = "c4579586f41a90372f762cb65c78be5d";
String urlStr = "http://api.flickr.com/services/rest/?" +
"method=flickr.interestingness.getList&per_page=20&api_key="+apiKey;
URL request = new URL(urlStr);
InputStream input = request.openStream();
|
上記のコードでは、Flickr の REST (Representational State Transfer) API を使用しています (Flickr の API および REST フォーマットについての詳細は「参考文献」を参照)。この呼び出しによる出力例をリスト 2 に記載します。
リスト 2. Flickr の XML
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<photos page="1" pages="25" per_page="20" total="500">
<photo id="469774979" owner="35373726@N00" secret="c8a1be2012" server="183"
farm="1" title="Where will it lead me......?" ispublic="1" isfriend="0"
isfamily="0" />
<photo id="470281793" owner="73955226@N00" secret="49612a2794" server="212"
farm="1" title="Island Beauty" ispublic="1" isfriend="0" isfamily="0" />
<photo id="469808244" owner="43568064@N00" secret="26b71544a3" server="227"
farm="1" title="" ispublic="1" isfriend="0" isfamily="0" />
</photos>
</rsp>
|
リスト 2 に記載しているのは 3 点の写真だけですが、この API 呼び出しが実際に返すのは 20 の結果です (URL ストリングの per_page パラメーター)。これらの結果は見てのとおり、とても簡単なので、この XML を構文解析する方法に目を移すことにしましょう。この例では、写真ごとのタイトルと ID を構文解析します。写真の URL は ID を使って作成できるため、この情報だけを使用する Web アプリケーション (おそらくマッシュアップ) は簡単に想像できるはずです。まず、DOM を使用してこのデータを抽出します。
DOM の例
DOM を使用するには、文書を構文解析して文書オブジェクトを作成します。このオブジェクトは、構文解析された XML 文書を表すメモリー内ツリー構造です。次に DOM ツリーを探索して各写真のタイトルと ID を見つけ、そのデータを単純なマップに挿入します。この一連の操作を行うコードは、リスト 3 のとおりです。
リスト 3. DOM による構文解析
Map<String,String> map = new HashMap<String,String>();
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document dom = builder.parse(input);
Element root = dom.getDocumentElement();
NodeList childNodes = root.getChildNodes();
Node photosNode = null;
for (int i=0;i<childNodes.getLength();i++){
Node node = childNodes.item(i);
if (node.getNodeName().equalsIgnoreCase("photos")){
photosNode = node;
break;
}
}
childNodes = photosNode.getChildNodes();
for (int i=0;i<childNodes.getLength();i++){
Node node = childNodes.item(i);
if (node.getNodeName().equalsIgnoreCase("photo")){
String title = node.getAttributes().getNamedItem("title").getTextContent();
String id = node.getAttributes().getNamedItem("id").getTextContent();
map.put(id,title);
}
}
|
DOM がよく使われる理由となっているのは、その使いやすさです。単に入力ソースをパーサーに渡すだけで、パーサーが document オブジェクトを作成してくれます。このオブジェクトが手に入ったら、写真ノードが見つかるまで子ノードをスキャンします。それぞれの写真ノードはその写真ノードの子なので、写真ノードを 1 つひとつ調べ、各写真ノードの title 属性と id 属性にアクセスしてマップに保存します。
このように 簡単に使える DOM ですが、その効率性については明らかな欠点があります。まず、写真の所有者など、関心のない多くのデータを保存することになるという点です。また、すべてのデータを 2 回読み取ることにもなります。つまり、文書オブジェクトに読み込むために 1 回、そして文書オブジェクトをウォークスルーするためにもう 1 回読み取らなければなりません。このような非効率性を避けるために従来から使われてきたのが、SAX です。
SAX の例
SAX パーサーは DOM パーサーのように使い勝手のいい document オブジェクトを返してはくれません。代わりに、XML 文書内部から一連のイベントが提供されるので、これらのイベントを処理するハンドラーを作成する必要があります。そのためには、インターフェースを実装するか、あるいは DefaultHandler クラスを継承し、必要に応じてこのクラスのメソッドをオーバーライドする必要があります。リスト 4 は、Flickr の XML 文書を SAX で構文解析する場合のコードです。
リスト 4. SAX による構文解析
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
DefaultHandler handler = new DefaultHandler(){
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("photo")){
String title = attributes.getValue("title");
String id = attributes.getValue("id");
// map is static so we can access it here
map.put(id, title);
}
}
};
parser.parse(input, handler);
|
リスト 4 に記載したコードは明らかに、リスト 3 の DOM コードより、やや理解しにくいです。上記では、SAX イベントを処理するための ContentHandler が必要なため、DefaultHandler を作成してその startElement コールバック・メソッドをオーバーライドしています。そして写真要素であるかどうかをチェックして、写真要素である場合はその title 属性と id 属性にアクセスしています。
このコードはかなり簡潔で、実行すると極めて効率的な動作をします。保存するのは関心のあるデータだけで、文書をスキャンするのも 1 回だけで済みます。しかしイベント・リスナーを登録するためにクラスを継承する必要があるため、コードが複雑になっています。XML を効率的に構文解析することが可能で、しかも一層直観的なプログラミング・モデルがあれば言うことありません。そこで登場するのが、StAX です。
StAX による代替策
SAX の複雑さは、SAX が実装するオブザーバー・デザイン・パターンによるものです。SAX は、パーサーがオブザーバーにイベントをプッシュし、オブザーバーがイベントを操作するという点ではプッシュ型モデルと言えます。StAX は SAX と同じく XML 文書のデータとイベントをストリーム化するため、SAX のように高速かつ効率的ですが、その大きな違いとして、StAX ではプル型モデルを使用します。そのため、アプリケーション・コードがパーサーからイベントをプルできるようになっています。
これは取るに足らない違いのように聞こえますが、この違いによってプログラミング・モデルが大幅に単純になります。実際の StAX をリスト 5 で見てください。
リスト 5. StAX による構文解析
Map<String,String> map = new HashMap<String,String>();
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
QName qId = new QName("id");
QName qTitle = new QName("title");
QName qPhoto = new QName("photo");
XMLEventReader reader = inputFactory.createXMLEventReader(input);
while (reader.hasNext()){
XMLEvent event = reader.nextEvent();
if (event.isStartElement()){
StartElement element = event.asStartElement();
if (element.getName().equals(qPhoto)){
String id = element.getAttributeByName(qId).getValue();
String title = element.getAttributeByName(qTitle).getValue();
map.put(id,title);
}
}
}
reader.close();
|
上記では第一に、クラスを継承する必要がなくなっています。その理由は、イベントにイベント・リスナー用のクラスを登録する必要がないためです。StAX では、イベントをパーサーからプルしてくることになるため、イベントのフローの制御を行うことになります。文書を検索して目的のデータを見つけるには、お馴染みのイテレーターのような構文を使用できます。保存しているのは同じく目的のデータだけで、XML 文書全体も 1 回スキャンするだけで済みます。このように、StAX には SAX と同じ効率性がありますが、コードは遥かに直観的です。
Geronimo の StAX プロバイダーとしての Woodstox
これで、StAX 構文解析の利点を理解していただけたはずです。StAX は XML 技術での重要な進歩として広く認められています。そのため、Java EE 5 仕様の一部となったのも当然のことでした (Java SE (Java Platform, Standard Edition) 6 の一部にもなっています)。Java EE 5 の一部である StAX は、Geronimo 2.0 でも実装しないわけにはいきません。
Geronimo チームにとって幸運なことに、オープン・ソースの StAX 実装には選択肢がいくつかありました。チームが Geronimo に組み込む StAX パーサーとして選んだのは、Woodstox です。Woodstox は極めて優れたパフォーマンスを持つ StAX 実装の 1 つとしてみなされています (出回っている各種 StAX パーサーの比較については、「参考文献」を参照してください)。その上、Woodstox は LGPL (Lesser General Public License) と Apache 2.0 ライセンスの両方で提供されるため、Woodstox とそのソース・コードを Geronimo に組み込む上での制約は何もありません。
アプリケーションのパフォーマンス調整: Woodstox を最大限活用する方法
Woodstox が Geronimo にもたらす利点の 1 つは、何と言ってもパフォーマンスです。ここで重要となるのは、他のハイ・パフォーマンス技術での場合と同じく、Woodstox によって最大限のパフォーマンスを引き出す方法を理解することです。リスト 5 のコードでは、StAX 仕様に含まれる上位レベルの API、XMLEventReader インターフェースを使用していますが、代わりに下位レベルの XMLStreamReader インターフェースを使えばパフォーマンスがさらに向上します。このインターフェースを使用した StAX パーサーは、リスト 6 のとおりです。
リスト 6. XMLStreamReader を使用した StAX による構文解析
Map<String,String> map = new HashMap<String,String>();
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
QName qId = new QName("id");
QName qTitle = new QName("title");
QName qPhoto = new QName("photo");
XMLStreamReader reader = inputFactory.createXMLStreamReader(input);
while (reader.hasNext()){
int event = reader.next();
if (event == START_ELEMENT){ // statically included constant from XMLStreamConstants
if (reader.getName().equals(qPhoto)){
String id = reader.getAttributeValue(null, qId.getLocalPart());
String title = reader.getAttributeValue(null, qTitle.getLocalPart());
map.put(id,title);
}
}
}
reader.close();
|
リスト 6 のコードはリスト 5 のコードと同様です。明らかに多少下位レベルで記述されたコードとなっていますが、パフォーマンスの大幅な向上を実現できます。
まとめ
この記事では、StAX パーサーを使って XML 文書を構文解析する上での利点をいくつか紹介しました。StAX は、SAX と DOM の中間の程よい手法となります。StAX を Geronimo 2.0 の一部として使用すれば、StAX の利点をすぐに生かすことができます。StAX の直観的なプル型 API だけでなく、Woodstox で StAX のハイパフォーマンス実装を使用するメリットも生かしてください。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample article code | renegade.woodstox.source.zip | 4KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Michael Galpin は 1998年からプロとして Java ソフトウェアを開発しています。現在は、カリフォルニア州 Mountain Viewにある新興企業、Adomo, Inc. に勤務しています。California Institute of Technology で数学の学位を取得しました。 |
記事の評価
|