我想从一个包含大量数据的对象(包含嵌套集合)生成 XML 文件。但是 XML 有一个限制,它不能超过 50MB。
有什么好方法可以做到这一点吗?
更新:速度不重要,主要的是每个文件分成 50MB
在我的工作中遇到了类似的要求。我的最大努力(直观,易于实施,相对性能)如下。我基本上用一个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 包含空格格式,则最后剩余的受害节点和关闭容器元素标记之间的任何空格都会丢失。这可以通过更改跳过子句(将j++
语句移动到跳过后)来缓解,但最终会得到额外的空格。上面介绍的解决方案生成源文件的最小文件大小副本。
您可以毫无问题地使用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();
}
您是否考虑过像字符串一样编写 XML 文件,而不是使用 .NET 中的 XML 支持。
我正在将大约 10GB 的数据写入 XML,因为这是工具可以使用它的唯一方式。
我遇到了这样的问题,但我的 XML 非常简单,我只使用了 TextWriter 并嵌套了 for 循环来编写 XML。
很有魅力,而且比 XML 对象快得多。