高效转换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#新手,所以我为我的代码道歉
一小时内一百万个文件?你怎么能期望比这更好呢?您目前每秒处理277个文件!
您将需要运行多个进程和/或机器来编写单独的文件,然后在最后编译它们以实现任何大的改进。
这个问题可能更适合于codereview,因为您的代码当前可以工作。话虽如此,我可以提出以下建议:
-
不要在您的1000000个文件中的每个
Parallel.ForEach()
中执行Console.Writeline()
!它很慢,而且阻塞。相反,可以考虑每十秒左右输出一次心跳消息,并且从一个不会干扰XML处理线程的单独线程输出。例如,从这里看到
NonBlockingConsole
-
与其将每个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; } }
-
Excel COM互操作也很慢,因为每个方法调用实际上是对另一个进程的RPC调用。因此,与其将每一行单独写入Excel,不如使用给定XML文件中的所有行创建一个2d数组(如上所述),并在单个块中将该2d数组写入Excel。要做到这一点,请参见例如写入数组到Excel范围或Excel互操作-效率和性能或Microsoft.Office.Interop.Excel真的很慢。
您也可以考虑在写入Excel之前将单个表分成更大的块,以进一步减少互操作调用的数量。如果您有1000000个文件,那将是至少 1000000个RPC调用。
-
此外,Excel COM互操作显然不是真正的多线程,根据这个答案,也是这个。相反,它是公寓线程的,如果需要,调用被封送到从其他线程创建COM对象的线程。
因此,考虑改变你的线程策略,使用多个生产者/单个消费者队列,就像这个问题或这个问题一样。在您的生产者线程中,从每个XML文件中提取必要的数据表作为
object [,]
。在单个消费者线程中,打开Excel文件,使用每个object [,]
表并将其作为单个2d范围写入Excel文件,然后最后关闭该文件。 -
现在你正在从一个线程中写入Excel,考虑完全放弃Excel COM互操作,使用Import and Export Excel的选项直接写入文件-什么是最好的库?或从c#创建Excel (. xls和. xlsx)文件。它甚至可以是一个简单的CSV文件!
在你当前的代码结构中,#1 - #3看起来很容易测试。#4和#5会更有挑战性。