【问题标题】:Most Efficient Way of Combining or Joining Objects Read From Separate XML Files组合或连接从单独的 XML 文件读取的对象的最有效方法
【发布时间】:2012-06-02 18:34:18
【问题描述】:

我每天早上都会收到大量以单独的 XML 文件形式接收的数据。我需要组合 XML 中的对象并从中生成报告。我正在寻找解决此问题的最佳解决方案。

为了证明我编造了以下例子:

有 2 个 XML 文件:

第一个是语言列表及其使用的国家/地区。第二个是产品列表及其销售国家/地区。我生成的报告是产品名称后跟包装必须使用的语言在。

XML1:

<?xml version="1.0" encoding="utf-8"?>
<languages>
  <language>
    <name>English</name>
    <country>8</country>
    <country>9</country>
    <country>3</country>
    <country>11</country>
    <country>12</country>
  </language>
  <language>
    <name>French</name>
    <country>3</country>
    <country>6</country>
    <country>7</country>
    <country>13</country>
  </language>
  <language>
    <name>Spanish</name>
    <country>1</country>
    <country>2</country>
    <country>3</country>
  </language>
</languages>

XML2:

<?xml version="1.0" encoding="utf-8"?>
<products>
  <product>
    <name>Screws</name>
    <country>3</country>
    <country>12</country>
    <country>29</country>
  </product>
  <product>
    <name>Hammers</name>
    <country>1</country>
    <country>13</country>
  </product>
  <product>
    <name>Ladders</name>
    <country>12</country>
    <country>39</country>
    <country>56</country>
  </product>
  <product>
    <name>Wrenches</name>
    <country>8</country>
    <country>13</country>
    <country>456</country>
  </product>
  <product>
    <name>Levels</name>
    <country>19</country>
    <country>18</country>
    <country>17</country>
  </product>
</products>

示例程序输出:

 Screws ->  English, French, Spanish
 Wrenches ->  English, French
 Hammer - > French, Spanish
 Ladders-> English

目前我反序列化为数据集,然后使用 linq 跨数据集连接以生成所需的报告字符串。 (如下所示 - 将文件名作为命令行参数传入)。

public static List<String> XMLCombine(String[] args)
{
    var output = new List<String>();
    var dataSets = new List<DataSet>();
    //Load each of the Documents specified in the args
    foreach (var s in args)
    {
        var path = Environment.CurrentDirectory + "\\" + s;
        var tempDS = new DataSet();
        try
        {
            tempDS.ReadXml(path);
        }
        catch (Exception ex)
        {
            //Custom Logging + Error Reporting
            return null;
        }
        dataSets.Add(tempDS);
    }
    //determine order of files submitted
    var productIndex = dataSets[0].DataSetName == "products" ? 0:1;
    var languageIndex = dataSets[0].DataSetName == "products" ? 1:0;
    var joined = from tProducts in dataSets[productIndex].Tables["product"].AsEnumerable()
                 join tProductCountries in dataSets[productIndex].Tables["country"].AsEnumerable() on (int)tProducts["product_id"] equals (int)tProductCountries["product_id"]
                 join tLanguageCountries in dataSets[languageIndex].Tables["country"].AsEnumerable() on (String)tProductCountries["country_text"] equals (String)tLanguageCountries["country_text"]
                 join tLanguages in dataSets[languageIndex].Tables["language"].AsEnumerable() on (int)tLanguageCountries["language_Id"] equals (int)tLanguages["language_Id"]
                  select new
                  {
                      Language = tLanguages["name"].ToString(),
                      Product = tProducts["name"].ToString()
                  };

    var listOfProducts = joined.OrderByDescending(_ => _.Product).Select(_ => _.Product).Distinct().ToList();

    foreach (var e in listOfProducts)
    {
        var e1 = e;
        var languages = joined.Where(_ => _.Product == e1).Select(_ => _.Language).Distinct().ToList();
        languages.Sort();
        //Custom simple Array to text method
        output.Add(String.Format("{0} {1}", e, ArrayToText(languages)));
    }
    return output;
}

这很好,但我知道必须有更优化的解决方案来解决这个问题(特别是当 XML 文件在现实生活中很大时)。有没有人有替代方法(除了 linq)的经验或关于优化当前方法的建议,这将使我更接近最佳解决方案?

非常感谢。

解决方案 实施建议的解决方案: Casperah 使用字典的方法在 312 毫秒内处理数据集。 yamen 的方法使用 Linq Lookup 在 452ms 内处理数据集。

【问题讨论】:

    标签: c# xml linq xml-serialization xml-parsing


    【解决方案1】:

    你有两个问题,内存使用和CPU使用。

    要限制内存使用,您可以使用 XmlReader,它只读取一小部分巨大的 xml 文件。 要限制 CPU 使用率,您应该有一个国家代码索引。

    我会这样做: 1.阅读所有语言并将其插入字典中,如下所示: // 键是国家,值是语言列表。 词典>国家=新词典>(); 2. 使用 XmlReader 一次读取一个产品 3. 查找国家并写出 Languages 可能使用 HashSet 以避免重复 Languages。

    这将是我的方法 - 祝你好运

    我已经创建了这个例子:

            Dictionary<int, List<string>> countries = new Dictionary<int, List<string>>();
    
            XmlReader xml = XmlReader.Create("file://D:/Development/Test/StackOverflowQuestion/StackOverflowQuestion/Countries.xml");
            string language = null;
            string elementName = null;
            while (xml.Read())
            {
                switch (xml.NodeType)
                {
                    case XmlNodeType.Element:
                        elementName = xml.Name;
                        break;
    
                    case XmlNodeType.Text:
                        if (elementName == "name") language = xml.Value;
                        if (elementName == "country")
                        {
                            int country;
                            if (int.TryParse(xml.Value, out country))
                            {
                                List<string> languages;
                                if (!countries.TryGetValue(country, out languages))
                                {
                                    languages = new List<string>();
                                    countries.Add(country, languages);
                                }
                                languages.Add(language);
                            }
                        }
                        break;
                }
            }
            using (StreamWriter result = new StreamWriter(@"D:\Development\Test\StackOverflowQuestion\StackOverflowQuestion\Output.txt"))
            {
                xml = XmlReader.Create("file://D:/Development/Test/StackOverflowQuestion/StackOverflowQuestion/Products.xml");
                string product = null;
                elementName = null;
                HashSet<string> languages = new HashSet<string>();
                while (xml.Read())
                {
                    switch (xml.NodeType)
                    {
                        case XmlNodeType.Element:
                            elementName = xml.Name;
                            break;
    
                        case XmlNodeType.Text:
                            if (elementName == "name")
                            {
                                if (product != null && languages != null)
                                {
                                    result.Write(product);
                                    result.Write(" -> ");
                                    result.WriteLine(string.Join(", ", languages.ToArray()));
                                    languages.Clear();
                                }
                                product = xml.Value;
                            }
                            if (elementName == "country")
                            {
                                int country;
                                if (int.TryParse(xml.Value, out country))
                                {
                                    List<string> countryLanguages;
                                    if (countries.TryGetValue(country, out countryLanguages))
                                        foreach (string countryLanguage in countryLanguages) languages.Add(countryLanguage);
                                }
                            }
                            break;
                    }
                }
            }
        }
    

    它产生了这个例子:

    Screws -> English, French, Spanish
    Hammers -> Spanish, French
    Ladders -> English
    Wrenches -> English, French
    

    XmlReader.Create 需要一个 uri,您也可以使用类似:“http://www.mysite.com/countries.xml”

    【讨论】:

    • 感谢您为此所做的工作。字典方法非常有效。
    【解决方案2】:

    在您的情况下,我会将语言文件中的数据存储到字典或类似的东西中,然后我会解析每个产品文件并即时生成最终的组合结果。我想这种方法会更快,并且可以避免大量数据出现的内存问题。

    【讨论】:

      【解决方案3】:

      好的,这仍然是 LINQ to XML,但我认为就算法而言它非常有效。唯一的问题是您的 XML 是否非常大(即大于 RAM 可以容纳的容量)。否则,它不会比这更快。

      假设languageFileproductFile 包含相关的XML 文件。

      将语言转换为查找:

      var languages = (from language in XElement.Load(languageFile).Descendants("language")
                      from country in language.Elements("country")
                      select new {Language = language.Element("name").Value, Country = country.Value})
                      .ToLookup(l => l.Country, l => l.Language);
      

      然后通过语言查找获取产品:

      var products = from product in XElement.Load(productFile).Descendants("product")
                     select new {Product = product.Element("name").Value, 
                                 Languages = product.Elements("country").SelectMany(e => languages[e.Value]).Distinct().ToList()};
      

      当然你也可以打印出来:

      foreach (var product in products.Where(x => x.Languages.Count > 0))
      {
          Console.WriteLine("{0} -> {1}", product.Product, String.Join(", ", product.Languages));
      }
      

      返回:

      Screws -> English, French, Spanish
      Hammers -> Spanish, French
      Ladders -> English
      Wrenches -> English, French
      

      【讨论】:

      • 谢谢。你的方法非常优雅,比我最初的尝试有了很大的改进。
      猜你喜欢
      • 1970-01-01
      • 2013-06-10
      • 1970-01-01
      • 1970-01-01
      • 2013-05-03
      • 2012-12-22
      • 2013-07-25
      • 2011-10-11
      • 1970-01-01
      相关资源
      最近更新 更多