我想从一个包含大量数据的对象(包含嵌套集合)生成XML文件。 但是XML存在限制,它不能超过50MB 。
有没有什么好方法可以做到这一点?
更新:速度并不重要,每个文件主要分为50MB
答案 0 :(得分:3)
在我的工作中遇到类似的要求。我的最大努力(直观,易于实施,相对高效)如下。我基本上用XmlWriter
编写,监视底层流。当它超过我的文件大小限制时,我完成当前的Xml片段,保存文件,关闭流。
然后在第二遍,我将完整的DOM加载到内存中,并迭代删除节点并保存文档,直到它的大小可以接受。
例如
// arbitrary limit of 10MB
long FileSizeLimit = 10*1024*1024;
// open file stream to monitor file size
using (FileStream file = new FileStream("some.data.xml", FileMode.Create))
using (XmlWriter writer = XmlWriter.Create(file))
{
writer.WriteStartElement("root");
// while not greater than FileSizeLimit
for (; file.Length < FileSizeLimit; )
{
// write contents
writer.WriteElementString(
"data",
string.Format("{0}/{0}/{0}/{0}/{0}", Guid.NewGuid()));
}
// complete fragment; this is the trickiest part,
// since a complex document may have an arbitrarily
// long tail, and cannot be known during file size
// sampling above
writer.WriteEndElement();
writer.Flush();
}
// iteratively reduce document size
// NOTE: XDocument will load full DOM into memory
XDocument document = XDocument.Load("some.data.xml");
XElement root = document.Element("root");
for (; new FileInfo("some.data.xml").Length > FileSizeLimit; )
{
root.LastNode.Remove();
document.Save("some.data.xml");
}
有办法改善这一点;如果内存是约束的一种可能性是重写迭代位以获取在第一遍中实际写入的节点的计数,然后重写文件少一个元素,并继续直到完整文档具有所需大小。
这最后一条建议可能是要走的路,特别是如果你已经需要跟踪写入的元素以便在另一个文件中继续写作。
希望这有帮助!
修改强>
虽然直观且易于实施,但我觉得值得研究上述优化。这就是我得到的。
一种帮助编写祖先节点(即容器节点和所有其他类型的标记)的扩展方法,
// performs a shallow copy of a given node. courtesy of Mark Fussell
// http://blogs.msdn.com/b/mfussell/archive/2005/02/12/371546.aspx
public static void WriteShallowNode(this XmlWriter writer, XmlReader reader)
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
writer.WriteStartElement(
reader.Prefix,
reader.LocalName,
reader.NamespaceURI);
writer.WriteAttributes(reader, true);
if (reader.IsEmptyElement)
{
writer.WriteEndElement();
}
break;
case XmlNodeType.Text: writer.WriteString(reader.Value); break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
writer.WriteWhitespace(reader.Value);
break;
case XmlNodeType.CDATA: writer.WriteCData(reader.Value); break;
case XmlNodeType.EntityReference:
writer.WriteEntityRef(reader.Name);
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
writer.WriteProcessingInstruction(reader.Name, reader.Value);
break;
case XmlNodeType.DocumentType:
writer.WriteDocType(
reader.Name,
reader.GetAttribute("PUBLIC"),
reader.GetAttribute("SYSTEM"),
reader.Value);
break;
case XmlNodeType.Comment: writer.WriteComment(reader.Value); break;
case XmlNodeType.EndElement: writer.WriteFullEndElement(); break;
}
}
和一个将执行修剪的方法(不是扩展方法,因为扩展任何参数类型会有点模糊)。
// trims xml file to specified file size. does so by
// counting number of "victim candidates" and then iteratively
// trimming these candidates one at a time until resultant
// file size is just less than desired limit. does not
// consider nested victim candidates.
public static void TrimXmlFile(string filename, long size, string trimNodeName)
{
long fileSize = new FileInfo(filename).Length;
long workNodeCount = 0;
// count number of victim elements in xml
if (fileSize > size)
{
XmlReader countReader = XmlReader.Create(filename);
for (; countReader.Read(); )
{
if (countReader.NodeType == XmlNodeType.Element &&
countReader.Name == trimNodeName)
{
workNodeCount++;
countReader.Skip();
}
}
countReader.Close();
}
// if greater than desired file size, and there is at least
// one victim candidate
string workFilename = filename+".work";
for (;
fileSize > size && workNodeCount > 0;
fileSize = new FileInfo(filename).Length)
{
workNodeCount--;
using (FileStream readFile = new FileStream(filename, FileMode.Open))
using (FileStream writeFile = new FileStream(
workFilename,
FileMode.Create))
{
XmlReader reader = XmlReader.Create(readFile);
XmlWriter writer = XmlWriter.Create(writeFile);
long j = 0;
bool hasAlreadyRead = false;
for (; (hasAlreadyRead) || reader.Read(); )
{
// if node is a victim node
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == trimNodeName)
{
// if we have not surpassed this iteration's
// allowance, preserve node
if (j < workNodeCount)
{
writer.WriteNode(reader, true);
}
j++;
// if we have exceeded this iteration's
// allowance, trim node (and whitespace)
if (j >= workNodeCount)
{
reader.ReadToNextSibling(trimNodeName);
}
hasAlreadyRead = true;
}
else
{
// some other xml content we should preserve
writer.WriteShallowNode(reader);
hasAlreadyRead = false;
}
}
writer.Flush();
}
File.Copy(workFilename, filename, true);
}
File.Delete(workFilename);
}
如果您的Xml包含空格格式,则最后剩余的受害节点和关闭容器元素标记之间的任何空格都将丢失。这可以通过改变skip子句(移动j++
语句跳过后)来缓解,但最后会得到额外的空格。上面介绍的解决方案生成源文件的最小文件大小副本。
答案 1 :(得分:2)
您可以使用 XmlWriter 或 XDocument 编写大型xml文件,而不会出现任何问题。
这是一个示例示例。此示例在不到5秒的时间内生成63MB xml文件。对于此示例,我使用类 XmlWriter 。
using (XmlWriter writer = XmlWriter.Create("YourFilePath"))
{
writer.WriteStartDocument();
writer.WriteStartElement("Root");
for (int i = 0; i < 1000000; i++) //Write one million nodes.
{
writer.WriteStartElement("Root");
writer.WriteAttributeString("value", "Value #" + i.ToString());
writer.WriteString("Inner Text #" + i.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
答案 2 :(得分:1)
您是否考虑过将XML文件写为字符串而不是使用.NET中的XML支持。
我正在向XML写入~10GB的数据,因为这是工具使用它的唯一方式。
我有这样的问题,但我的XML非常简单,我只使用了TextWriter并嵌套for循环来编写XML。
工作了一个魅力,加上比XML对象快得多。