2011-12-15 75 views
8

我需要处理由大量独立记录组成的XML文档,例如,如何从大型XML文档获取流式Iterator [Node]?

<employees> 
    <employee> 
     <firstName>Kermit</firstName> 
     <lastName>Frog</lastName> 
     <role>Singer</role> 
    </employee> 
    <employee> 
     <firstName>Oscar</firstName> 
     <lastName>Grouch</lastName> 
     <role>Garbageman</role> 
    </employee> 
    ... 
</employees> 

在某些情况下,这些都只是大文件,但其他人,他们可能来自数据流源。

我不能只是scala.xml.XmlLoader.load()它,因为我不想保存整个文档在内存中(或等待输入流关闭),当我只需要工作一次一个记录。我知道我可以使用XmlEventReader将输入流作为一系列XmlEvent进行流式处理。然而,这些工作比scala.xml.Node更不方便。

所以我想获得一个懒惰的迭代器[节点]出这个不知何故,为了使用上的方便Scala的语法每一个人记录进行操作,同时保持控制下的内存使用情况。

要做到这一点我自己,我可以XMLEventReader的开始,建立每个匹配的开始和结束标记之间的事件的缓冲区,然后从构建一个节点树。但是,有没有更容易忽视的方法?感谢任何见解!

回答

8

您可以使用XMLEventReaderConstructingParser使用的底层解析器,并使用回调处理您的员工节点在顶层以下。你必须尽快处理,要小心删除数据:

import scala.xml._ 

def processSource[T](input: Source)(f: NodeSeq => T) { 
    new scala.xml.parsing.ConstructingParser(input, false) { 
    nextch // initialize per documentation 
    document // trigger parsing by requesting document 

    var depth = 0 // track depth 

    override def elemStart(pos: Int, pre: String, label: String, 
     attrs: MetaData, scope: NamespaceBinding) { 
     super.elemStart(pos, pre, label, attrs, scope) 
     depth += 1 
    } 
    override def elemEnd(pos: Int, pre: String, label: String) { 
     depth -= 1 
     super.elemEnd(pos, pre, label) 
    } 
    override def elem(pos: Int, pre: String, label: String, attrs: MetaData, 
     pscope: NamespaceBinding, nodes: NodeSeq): NodeSeq = { 
     val node = super.elem(pos, pre, label, attrs, pscope, nodes) 
     depth match { 
     case 1 => <dummy/> // dummy final roll up 
     case 2 => f(node); NodeSeq.Empty // process and discard employee nodes 
     case _ => node // roll up other nodes 
     } 
    } 
    } 
} 

然后你使用这样的处理在固定存储器中的第二级的每个节点(假设在第二级的节点都没有得到一个孩子的任意数):

processSource(src){ node => 
    // process here 
    println(node) 
} 

相比XMLEventReader的好处是,你不使用两个线程。与建议的解决方案相比,您也不必解析节点两次。缺点是这依赖于ConstructingParser的内部工作。

+0

辉煌!这很好。从这个生成器风格的东西到一个迭代器并不难;看到我的其他答案。非常感谢! – 2011-12-16 18:21:04

5

从huynhjl的发电机解决了TraversableOnce[Node]获取,使用this trick

def generatorToTraversable[T](func: (T => Unit) => Unit) = 
    new Traversable[T] { 
    def foreach[X](f: T => X) { 
     func(f(_)) 
    } 
    } 

def firstLevelNodes(input: Source): TraversableOnce[Node] = 
    generatorToTraversable(processSource(input)) 

generatorToTraversable的结果是不可遍历超过一次(即使新ConstructingParser在每个的foreach调用实例化),因为输入流是一个源,它是一个迭代器。不过,我们不能重写Traversable.isTraversableAgain,因为它是最终的。

真的,我们想通过只返回一个迭代器来执行此操作。但是,Traversable.toIterator和Traversable.view.toIterator都会创建一个中间流,它将缓存所有条目(破坏本练习的全部目的)。好吧;如果访问了两次,我会让流引发异常。

还要注意整个事情是不是线程安全的。

此代码运行非常好,我相信整体解决方案既懒惰和不缓存(因此常量内存),虽然我还没有尝试过在一个大的输入呢。

+0

我不知道这个真棒技巧! – huynhjl 2011-12-17 02:37:52