Проблема с производительностью чтения и анализа больших файлов XML - PullRequest
0 голосов
/ 31 декабря 2018

У меня есть каталог, который содержит несколько больших файлов XML (общий размер около 10 ГБ).Есть ли способ перебрать каталог, содержащий файлы 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 голосов
/ 31 декабря 2018

Вы можете сделать что-то еще лучше: вы можете токенизировать свои 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

0 голосов
/ 31 декабря 2018

Пакет encoding/xml обеспечивает тип среднего уровня xml.Decoder.Это позволяет вам читать входной поток XML по одному Token за раз, мало чем отличаясь от старой потоковой модели Java SAX.Когда вы найдете то, что ищете, вы можете вернуться к decoder.Decode, чтобы запустить обычную последовательность демаршалинга, чтобы вывести отдельные объекты.Просто помните, что поток токенов может содержать несколько «нерелевантных» вещей (текстовые узлы только для пробелов, инструкции по обработке, комментарии), и вам нужно пропустить их, по-прежнему ища «важные» вещи (текст без пробелов)узлы, неожиданные элементы начала / конца).

В качестве примера высокого уровня, если вы ожидаете очень большое сообщение SOAP со списком записей, вы можете выполнять потоковый анализ до тех пор, пока не увидите<soap:Body> start-element, проверьте, что его непосредственный дочерний элемент ( например, , следующий start-элемент) является ожидаемым элементом, и затем вызовите decoder.Decode для каждого из его дочерних элементов.Если вы видите конец элемента операции, вы можете развернуть дерево элементов (теперь вы ожидаете увидеть </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, а в чем-то меньшем).

Синтаксический анализ XML в Go, как и в принципе все остальное, представляет собой «вытягивающую» модель: вы говорите читателю, что читать, и он получает данные из io.ReaderВы даете это.Если вы вручную создаете xml.Decoder, вы можете извлечь из него один токен за один раз, и это, вероятно, вызовет r.Read в удобоваримых кусках, но вы не можете вставлять крошечные приращения данных в анализатор, как вы предлагаете.

Я не могу конкретно говорить о производительности encoding/xml, но подобный подход с гибридной потоковой передачей, по крайней мере, увеличит задержку при первом выводе и сохранит меньше оперативных данных в памяти за один раз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...