我的问题
我需要阅读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#的新手,所以我为我的代码道歉
答案 0 :(得分:0)
你需要运行多个进程和/或机器写入单独的文件,然后在最后编译它们以实现任何重大改进。
答案 1 :(得分:0)
此问题可能更适合codereview,因为您的代码目前有效。话虽如此,我可以提出以下建议:
对于每个1000000文件,不要在Console.Writeline()
内Parallel.ForEach()
!它是slow和blocking。
相反,请考虑每十秒钟左右输出一次心跳消息,并从不会干扰XML处理线程的单独线程执行此操作。例如,请参阅here中的NonBlockingConsole
。
不是将每个XML文件加载到XDocument
,而是可以使用XmlReader
仅在给定时间内将XElement
加载到内存中所需的最小值来流式传输每个文件,沿{ {3}}。这应该通过跳过不需要的object[,] table
子树的构造并通过降低GC压力间接地提高性能。
以下方法流式传输您的某个XML文件,并在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;
}
}
使用扩展方法:
object [,]
Excel COM Interop也很慢,因为每个方法调用实际上都是对另一个进程的RPC调用。因此,不是单独将每一行写入Excel,而应创建一个包含给定XML文件中所有行的二维数组(如上所述),并在单个块中将该二维数组写入Excel。为此,请参阅How to: Stream XML Fragments from an XmlReader或Write Array to Excel Range或Excel Interop - Efficiency and performance。
在写入Excel之前,您还可以考虑将各个表分块为更大的块,以进一步减少Interop调用的数量。如果您有1000000个文件,那么至少 1000000个RPC调用。
此外,根据Microsoft.Office.Interop.Excel really slow和this answer,Excel COM Interop显然不是真正的多线程。相反,它是单元线程,并且调用被编组到从其他线程创建COM对象的线程(如果需要)。
因此,请考虑更改线程策略以使用多个生产者/单个使用者队列,沿this one或this question行。
在生产者线程中,从每个XML文件中提取必要的数据表object [,]
。在单个使用者线程中,打开Excel文件,使用每个public JsonDocument put(final String key, final JsonObject googleJsonObject) {
try {
JsonDocument document = JsonDocument.create(key,
com.couchbase.client.java.document.json.JsonObject.fromJson(googleJsonObject.toString()));
return bucket.upsert(document);
} catch (Throwable th) {
return null;
}
}
public String get(final String key) {
JsonDocument d = bucket.get(JsonDocument.create(key));
if (d != null) {
return d.content().toString();
} else {
return null;
}
}
表并将其作为单个2d范围写入Excel文件,然后最终关闭该文件。这应该避免任何编组惩罚。
现在您要从单个线程写入Excel,请考虑完全放弃Excel COM Interop并使用this one或Import and Export Excel - What is the best library?中的选项直接写入文件。它甚至可以是一个简单的CSV文件!
其中,考虑到您当前的代码结构,#1 - #3看起来很简单。 #4和#5会更具挑战性。