读取和解析大型XML文件的性能问题

时间:2018-12-31 10:21:43

标签: xml go

我有一个目录,其中包含几个大型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)
}

2 个答案:

答案 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>