我需要处理由大量独立记录组成的XML文档,例如
<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将输入作为一系列XmlEvents流式传输。但是,与scala.xml.Node相比,使用它们要方便得多。
所以我想以某种方式得到一个懒惰的Iterator [Node],以便使用方便的Scala语法对每个单独的记录进行操作,同时保持内存使用的控制。
为了自己做,我可以从一个XmlEventReader开始,在每个匹配的开始和结束标记之间建立一个事件缓冲区,然后从中构建一个Node树。但是,有一种我更容易被忽视的方式吗?感谢您的任何见解!
答案 0 :(得分:8)
您可以使用XMLEventReader
到ConstructingParser
使用的基础解析器,并使用回调处理顶层以下的员工节点。您只需要小心处理完毕后丢弃数据:
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
的内部运作。
答案 1 :(得分: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的结果不能遍历多次(即使在每个foreach调用上实例化一个新的ConstructingParser),因为输入流是一个Source,它是一个Iterator。但是,我们无法覆盖Traversable.isTraversableAgain,因为它是最终的。
我们真的想通过返回Iterator来强制执行此操作。但是,Traversable.toIterator和Traversable.view.toIterator都会生成一个中间Stream,它将缓存所有条目(无法完成本练习的全部目的)。那好吧;如果它被访问了两次,我会让流引发异常。
另请注意,整个事情并非线程安全。
这段代码运行得很好,我相信整个解决方案都是懒惰而不是缓存(因此是常量内存),尽管我还没有在大输入上尝试过它。