显示XML数据是集成B2B应用程序的重要角色。一般应用程序可以实用SAX或者DOM的方式解析XML文件。在单线程的环境下解析XML的程序是非常简单的,但在多线程的模式下,这项工作就会变得相当的复杂。比如在应用服务器一般会创建一个专用的线程去解析XML数据,并将解析数据提供给一个或多个其他线程使用。现在我们开始实现这样的XML解析程序.
Design Approach(设计实现) 按照producer-consumer的程序设计思想,需要一个专门的线程来扮演生成者去解析XML数据,而另外的一个线程组则充当消费者的角色。生成者在解析XML数据的同时保存结果一个共享的数据结构中,以供消费者来获取当前的处理结果。这个设计使用一个特殊的队列让生成者和消费者可以分别保持和获取解析的结果,以达到最大的吞吐量和最小的内存消耗。
Smart Queuing SmartQueue类为producer-consumer线程组提供队列服务。SmartQueue类的主要功能就是管理队列的长度。换句话说,就是使用固定长度队列策略有效的维护资源。通过挂起和唤醒适当的线程以保证队列的长度。比如当需要添加数据是,发现已经没有位置,则挂起生产者线程,直到有消费者线程移除一些数据。 下面的代码片段为策略的实现:
public synchronized void put(Object data) { // check to see if the length is 2 while (list.size() >= 2) { try { System.out.println("Waiting to put data"); wait(); } catch (Exception ex) { } }
list.add(data); notifyAll(); }
public synchronized Object take() { // wait until there is data to get // come out if the end of file signaled while (list.size() <= 0 && (eof != true)) { try { System.out.println("Waiting to consume data"); wait(); } catch (Exception ex) { } }
Object obj = null;
if (list.size() > 0) { obj = list.remove(0); } else { System.out.println("Woke up because end of document"); }
notifyAll(); return obj; }
XML Parsing 为什么使用SAX的方式来解析XML数据 不创建任何的内部XML数据描述,而只是在遇见元素是简单的将数据发送到应用程序 随最快和最高效的读取XML数据的方法 API很时候producerconsumer模型 XMLParserHandler类继承SAX,实现了通过方法回调的方式获取解析的XML数据。当XMLParserHandler将从Parser中获取的数据保存到Hahstablezhong。当到Element的结尾处时,则将Hashtable对象put进SmartQueue队列。当SmartQueue没有空闲位置时,Handler则进入等待状态直到消费线程移除数据后,put方法才可以完成。当XML文档完全解析后,XMLParseHandler会通知消费线程停止寻找更多的数据。 让我们着眼于如何用回调的方法向SmartQueue中保存数据并唤醒等待的消费者线程。StartElemetn方法为每个Elemetn实例化一个新的Hashtable. public void startElement( String namespaceURI, String localName, String qName, Attributes atts ) throws SAXException { System.out.println( " startElement local names............." + localName + " " + qName); if (qName.equalsIgnoreCase(elemmark)) { doc = new Hashtable(); } elem = qName; }
在endElement方法中则负责将解析的XML数据添到SmartQueue队列中。就象上面提到的,SmartQueue队列hold当前的线程到有空闲空间存储数据。 public void endElement( String namespaceURI, String localName, String qName ) throws SAXException { String s = sbData.toString();
System.out.println("element " + elem + " character " + s);
if ((doc != null) & (s != null) & !(s.trim().equals(""))) doc.put(elem, s);
sbData = new StringBuffer(); System.out.println(" endElement ending element............." + qName);
if (qName.equalsIgnoreCase(elemmark)) { System.out.println( " endElement ending element............." + localName);
smartQueue.put(doc); doc = null; } }
最后在endDocument的回调方法中通知所有的消费者线程文档解析结束。这意味这所有的消费者线程在其结束自己工作之前,不再需要等待更多的数据 public void endDocument() throws SAXException { smartQueue.end(); System.out.println("End Document............."); }
Consumer Threads 消费者线程从SmartQueue队列中读取生产者线程存储的数据。当SmartQueu队列中没有数据是,所有的消费者线程都进入等待状态。当生产者线程发出文档解析结束的信号和SmartQueue队列中的数据为空时,消费线程结束。下面的代码就实现的如何让消费线程在没有数据的时候等待,而在文件解析结束和SmartQueue中不在有任何数据的时候结束线程运行。 public void run() { while (!queue.isEmpty() || !queue.onEnd()) { Hashtable val = (Hashtable) queue.take();
System.out.println("Obtained by " + this.getName() + " " + val);
// try { // System.out.println("Simulate lengthy processing..........."); // Thread.sleep(2000); // } // catch(Exception ex){} } }
Benefits 这个设计有以下的优点 解析数据和使用数据可以并行处理 可以用较小的内存使用量,解析大的XML文件
Extending the Design 现在的SmartQueue是在定长的策略下有效的管理内存数据,我们可以定义不同的策略实现Take和Put方法。就象上面提到的,可以建立专用的对象来代替Hashtable来保存XMLParserHandler解析的XML元素和值。
PS 我知道我翻译的水平很烂,希望大家可以指正。只是感觉这篇文章和设计模式结合的很好介绍给大家,如果有任何评论欢迎到 用代码说话,那个是我的新blog
| |