使用 XML |
|
(这是 LazyKit 的一部分。)
参见 XmlIter 获取这些函数的非消耗版本。
xpairs_c(tree)
、xnpairs_c(tree)
和 xmliter.switch_c(parent, ftable, [opts])
也会遍历 tree
的子节点,但它们在处理过程中会消耗 tree
的子节点。以下两个片段具有相似的语义
for i,x in xpairs(parent) do parent[i] = nil do_something_with(x) end for i,x in xpairs_c(parent) do do_something_with(x) end
使用消耗迭代器意味着您不关心通过父节点访问先前处理过的树。但是,您仍然可以保存当前树以供以后使用
for i,x,name in xnpairs(parent) do if x.name == "xref" then table.insert(references, x) end end
使用消耗迭代器的主要原因是减少内存使用。当使用传统的 XML 树时,如果您在拆卸 XML 树的同时构建另一个数据结构,这可能会有所帮助;您已经处理过的树的一部分有资格进行垃圾回收,为您的新结构节省空间。
但是,当使用 LazyTree XML 树时,内存使用量可能会大大减少。考虑处理一个大型日志文件
<log> <entry>[....]</entry> <entry>[....]</entry> [...millions of elements later...] <entry>[....]</entry> </log>
使用传统的 XML 树,处理此文件需要与所有 <entry>
元素的大小成线性比例的空间。使用普通迭代器和延迟树,这需要与所有先前处理过的 <entry>
元素的大小成线性比例的空间(因为未来的元素仅在需要时读取)。使用消耗迭代器和延迟树,处理仅需要与单个 <entry>
元素的大小成比例的空间,因为先前处理过的 <entry>
已被遗忘。
消耗迭代器的另一个好处是它们可以稍微减少 CPU 使用量。当存在较少的活动数据时,Lua 5.0 垃圾收集器在收集期间不必那么努力。(???重新阅读 GC 算法以确保这是真的,尽管有计时数字。)
这里真正发生的事情是迭代器为表提供了基于事件的接口。消耗迭代器提供了与纯基于事件的 XML 解析器相同的许多好处,同时允许您在有意义的情况下流畅地切换回基于树的 API。
一个具体的例子是 XML-RPC 的 <value>
节点。如果它们包含一个元素,例如 <integer>
,那么这就是 <value>
节点的值。否则,<value>
元素的字符数据内容是一个字符串类型的值。处理该元素的代码可能需要向前查看任意数量的初始字符数据节点,以确定内部是否存在元素,或者它必须默认为字符串内容。基于事件的策略可以将元素的子节点列表视为一个任意长度的预读缓冲区。
用非消耗迭代器替换消耗迭代器始终是安全的;唯一的可能后果是在处理大型文档时内存耗尽。
只有在处理树的最后一步时,使用消耗迭代器才是最合理的。由于延迟 XML 树的工作方式,在调用消耗迭代器之前触摸子节点并不算错误。
在递归处理元素时,只有在你确定调用者不再关心其内容时,才应该调用消耗迭代器。一个经验法则是,只在另一个消耗迭代器内部调用消耗迭代器。