高效转换XML到Excel,选择一些字段

本文关键字:选择 字段 Excel 转换 XML 高效 | 更新日期: 2023-09-27 17:53:13

My Problem

我需要读取1000000个XML文件,每个文件提取一些信息,然后用这些信息创建一个电子表格。目前我有一个可以工作的代码,但是,它需要大约一个小时……我需要快速生成

XML文件示例

http://pastebin.com/M5uvVaYt(太大了)

我的当前代码

        string[] arquivosArr = Directory.GetFiles(@"D:'ALL_FILES", "*.xml", SearchOption.AllDirectories);
        List<string> arquivos = new List<string>(arquivosArr);
        XNamespace ns = "http://www.portalfiscal.inf.br/nfe";

        //EXCEL OBJ
        var excel = new Application();
        excel.DisplayAlerts = false;
        var workbooks = excel.Workbooks;
        var workbook = workbooks.Add(Type.Missing);
        var worksheets = workbook.Sheets;
        var worksheet = (Worksheet)worksheets[1];
        worksheet.Columns[58].NumberFormat = "@";
        var watch = System.Diagnostics.Stopwatch.StartNew();
        int i = 0;
        Parallel.ForEach(arquivos, arquivo =>            
        {
             try
            {
                var doc = XDocument.Load(arquivo);
                if (doc.Root.Name.LocalName == "nfeProc")
                {
                    var chave = doc.Descendants(ns + "chNFe").First().Value;
                    var itens = doc.Descendants(ns + "det");
                    //var info3 = .......
                    //var info4 = .......
                    //var info5 = .......
                    //var info6 = .......
                    //var info7 = .......
                    //var info8 = .......
                    //etc......

                    int starts = i;
                    Interlocked.Add(ref i, itens.Count());
                    foreach (var item in itens)
                    {
                        var data = new object[1, 58];
                        //data[0, 0] = .....
                        //data[0, 1] = .....
                        //data[0, 2] = .....
                        //data[0, 3] = .....
                        //data[0, 4] = .....
                        //data[0, 5] = .....
                        //data[0, 6] = .....
                        data[0, 27] = item.Attribute("nItem").Value;
                        data[0, 57] = chave;
                        var startCell = (Range)worksheet.Cells[(starts + 1), 1];
                        var endCell = (Range)worksheet.Cells[(starts + 1), 58];
                        var writeRange = worksheet.Range[startCell, endCell];
                        writeRange.Value2 = data;
                        starts++;
                    }

                        double perc = ((i + 1.00) / arquivos.Count) * 100;
                        Console.WriteLine("Add: " + (i + 1) + " (" + Math.Round(perc, 2) + "%)");                    

                }
            }
            catch (XmlException ex)
            {
                Console.WriteLine(ex.Message);
            }

            });
        watch.Stop();
        var elapsedMs = watch.ElapsedMilliseconds;
        Console.WriteLine(elapsedMs / 1000.0);

        workbook.SaveAs(@"D:'MY_INFO.xls");
        workbook.Close();
        excel.Quit();

我是c#新手,所以我为我的代码道歉

高效转换XML到Excel,选择一些字段

一小时内一百万个文件?你怎么能期望比这更好呢?您目前每秒处理277个文件!

您将需要运行多个进程和/或机器来编写单独的文件,然后在最后编译它们以实现任何大的改进。

这个问题可能更适合于codereview,因为您的代码当前可以工作。话虽如此,我可以提出以下建议:

  1. 不要在您的1000000个文件中的每个Parallel.ForEach()中执行Console.Writeline() !它很慢,而且阻塞。

    相反,可以考虑每十秒左右输出一次心跳消息,并且从一个不会干扰XML处理线程的单独线程输出。例如,从这里看到NonBlockingConsole

  2. 与其将每个XML文件加载到XDocument中,不如使用XmlReader流式传输每个文件,在给定时间只将所需的最小部分加载到内存中,方法如下:如何:从XmlReader流式传输XML片段。这将通过跳过不必要的XElement子树的构建直接提高性能,并通过减少GC压力间接提高性能。

    下面的方法通过您的一个XML文件,并在object[,] table中返回您选择的值:

    const int ColumnLength = 58;
    const int ChaveIndex = 57;
    const int ItemIndex = 27;
    static bool TryExtractTable(string arquivo, out object[,] table)
    {
        XNamespace ns = "http://www.portalfiscal.inf.br/nfe";
        var rootName = ns + "nfeProc";
        var chaveName = ns + "chNFe";
        var itemsName = ns + "det";
        try
        {
            using (var reader = XmlReader.Create(arquivo))
            {
                // Move to the root element, verify it's correct.
                if (!reader.ReadToElement() || reader.XName() != rootName)
                {
                    table = null;
                    return false;
                }
                string chaveValue = null;
                List<object> itemValues = new List<object>();
                bool alreadyReadNext = false;
                while (alreadyReadNext || reader.Read())
                {
                    alreadyReadNext = false;
                    if (reader.NodeType != XmlNodeType.Element)
                        continue;
                    var name = reader.XName();
                    if (chaveValue == null && name == chaveName)
                    {
                        chaveValue = ((XElement)XNode.ReadFrom(reader)).Value;
                        // XNode.ReadFrom advances the reader to the next node after the end of the current element.  
                        // Thus a subsequent call to reader.Read() would skip this node, and so should not be made.
                        alreadyReadNext = true;
                    }
                    else if (name == itemsName)
                    {
                        // Access the "nItem" attribute directly.
                        var itemValue = reader["nItem"];
                        itemValues.Add(itemValue);
                    }
                }
                if (itemValues.Count > 0)
                {
                    var nRows = itemValues.Count;
                    table = new object[nRows, ColumnLength];
                    for (int iRow = 0; iRow < nRows; iRow++)
                    {
                        table[iRow, ChaveIndex] = chaveValue;
                        table[iRow, ItemIndex] = itemValues[iRow];
                    }
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        table = null;
        return false;
    }
    

    使用扩展方法

    public static class XmlReaderExtensions
    {
        public static XName XName(this XmlReader reader)
        {
            return System.Xml.Linq.XName.Get(reader.LocalName, reader.NamespaceURI);
        }
        public static bool ReadToElement(this XmlReader reader)
        {
            while (reader.NodeType != XmlNodeType.Element)
                if (!reader.Read())
                    return false;
            return true;
        }
    }
    
  3. Excel COM互操作也很慢,因为每个方法调用实际上是对另一个进程的RPC调用。因此,与其将每一行单独写入Excel,不如使用给定XML文件中的所有行创建一个2d数组(如上所述),并在单个块中将该2d数组写入Excel。要做到这一点,请参见例如写入数组到Excel范围或Excel互操作-效率和性能或Microsoft.Office.Interop.Excel真的很慢。

    您也可以考虑在写入Excel之前将单个表分成更大的块,以进一步减少互操作调用的数量。如果您有1000000个文件,那将是至少 1000000个RPC调用。

  4. 此外,Excel COM互操作显然不是真正的多线程,根据这个答案,也是这个。相反,它是公寓线程的,如果需要,调用被封送到从其他线程创建COM对象的线程。

    因此,考虑改变你的线程策略,使用多个生产者/单个消费者队列,就像这个问题或这个问题一样。

    在您的生产者线程中,从每个XML文件中提取必要的数据表作为object [,]。在单个消费者线程中,打开Excel文件,使用每个object [,]表并将其作为单个2d范围写入Excel文件,然后最后关闭该文件。

  5. 现在你正在从一个线程中写入Excel,考虑完全放弃Excel COM互操作,使用Import and Export Excel的选项直接写入文件-什么是最好的库?或从c#创建Excel (. xls和. xlsx)文件。它甚至可以是一个简单的CSV文件!

在你当前的代码结构中,#1 - #3看起来很容易测试。#4和#5会更有挑战性。