我有一个目录,其中包含几个大型XML文件(总大小约为10 GB)。有什么方法可以遍历包含XML文件的目录并读取50字节乘50字节并以高性能解析XML文件吗?
func (mdc *Mdc) Loadxml(path string, wg sync.WaitGroup) {
defer wg.Done()
//var conf configuration
file, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
buf := make([]byte, 1024*1024)
scanner.Buffer(buf, 50)
for scanner.Scan() {
_, err := file.Read(buf)
if err != nil {
log.Fatal(err)
}
}
err = xml.Unmarshal(buf, &mdc)
if err != nil {
log.Fatal(err)
}
fmt.Println(mdc)
}
答案 0 :(得分:4)
您可以做得更好:您可以标记xml文件。
假设您有一个这样的xml
<inventory>
<item name="ACME Unobtainium">
<tag>Foo</tag>
<count>1</count>
</item>
<item name="Dirt">
<tag>Bar</tag>
<count>0</count>
</item>
</inventory>
您实际上可以拥有以下数据模型
type Inventory struct {
Items []Item `xml:"item"`
}
type Item struct {
Name string `xml:"name,attr"`
Tags []string `xml:"tag"`
Count int `xml:"count"`
}
现在,您要做的就是使用filepath.Walk并对要处理的每个文件执行以下操作:
decoder := xml.NewDecoder(file)
for {
// Read tokens from the XML document in a stream.
t, err := decoder.Token()
// If we are at the end of the file, we are done
if err == io.EOF {
log.Println("The end")
break
} else if err != nil {
log.Fatalf("Error decoding token: %s", err)
} else if t == nil {
break
}
// Here, we inspect the token
switch se := t.(type) {
// We have the start of an element.
// However, we have the complete token in t
case xml.StartElement:
switch se.Name.Local {
// Found an item, so we process it
case "item":
var item Item
// We decode the element into our data model...
if err = decoder.DecodeElement(&item, &se); err != nil {
log.Fatalf("Error decoding item: %s", err)
}
// And use it for whatever we want to
log.Printf("'%s' in stock: %d", item.Name, item.Count)
if len(item.Tags) > 0 {
log.Println("Tags")
for _, tag := range item.Tags {
log.Printf("\t%s", tag)
}
}
}
}
}
使用虚拟XML的工作示例:https://play.golang.org/p/MiLej7ih9Jt
答案 1 :(得分:2)
encoding/xml
软件包提供了中等级别的xml.Decoder
类型。这样一来,您一次就可以一次遍历一个Token
的XML输入流,这与旧的流Java SAX模型不同。找到所需的内容后,您可以跳回到decoder.Decode
,以执行正常的编组序列将单个对象取出。请记住,令牌流可能包含一些“不相关”的内容(仅限空白文本节点,处理指令,注释),您需要跳过它们,同时仍要查找“重要”内容(非空白文本)节点,意外的开始/结束元素)。
作为一个高级示例,如果您期望包含记录列表的SOAP消息非常大,则可以进行“流式”解析,直到看到<soap:Body>
起始元素,然后检查其起始元素。直接子项( eg ,下一个开始元素)是您期望的元素,然后在其每个子元素上调用decoder.Decode
。如果看到operation元素的末尾,则可以展开元素树(现在希望看到</soap:Body></soap:Envelope>
)。其他任何事情都是错误,您需要捕获并处理。
这里的应用程序框架可能看起来像
type Foo struct {
Name string `xml:"name"`
}
decoder := xml.NewDecoder(r)
for {
t, err := decoder.Token()
if err != nil {
panic(err)
}
switch x := t.(type) {
case xml.StartElement:
switch x.Name {
case xml.Name{Space: "", Local: "foo"}:
var foo Foo
err = decoder.DecodeElement(&foo, &x)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", foo)
default:
fmt.Printf("Unexpected SE {%s}%s\n", x.Name.Space, x.Name.Local)
}
case xml.EndElement:
switch x.Name {
default:
fmt.Printf("Unexpected EE {%s}%s\n", x.Name.Space, x.Name.Local)
}
}
}
https://play.golang.org/p/_ZfG9oCESLJ有一个完整的工作示例(不是SOAP情况,而是更小的示例)。
和其他基本内容一样,Go中的 XML解析是一个“拉”模型:您告诉读者要读取的内容,并从您提供的io.Reader
中获取数据。如果您手动创建一个xml.Decoder
,则可以一次从中提取一个令牌,这大概会以可消化的块形式调用r.Read
,但是当您将少量增量数据推入解析器时,求婚。
我不能特别谈谈encoding/xml
的性能,但是像这样的混合流方法将至少使您对第一个输出的延迟更好,并且一次将更少的实时数据保留在内存中。 / p>