大量のデータを含む 1 つのオブジェクト (ネストされたコレクションを含む) から XML ファイルを生成したいと考えています。ただし、XML には50MB を超えることができないという制限があります。
これを行う良い方法はありますか?
更新:速度は重要ではありません。主なものは、ファイルごとに50MBに分割されます
私の仕事で同様の要件に遭遇しました。私の最善の努力(直感的、実装の容易さ、比較的パフォーマンスの高い)は次のとおりです。私は基本的にXmlWriter
、基になるストリームを監視しながら、を使用して記述します。ファイルサイズの制限を超えたら、現在のXmlフラグメントを完成させ、ファイルを保存し、ストリームを閉じます。
次に、2回目のパスで、完全な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");
}
これを改善する方法があります。メモリが制約である場合の1つの可能性は、最初のパスで実際に書き込まれたノードの数を取得するために反復ビットを書き直してから、ファイルから1要素を差し引いて書き直し、ドキュメント全体が目的のサイズになるまで続行することです。
この最後の推奨事項は、特に別のファイルへの書き込みを再開するために書き込まれた要素を追跡する必要がある場合は、進むべき道かもしれません。
お役に立てれば!
編集
直感的で実装も簡単ですが、上記の最適化を検討する価値があると感じました。これは私が得たものです。
祖先ノード(つまり、コンテナノードや他のすべての種類のマークアップ)の記述に役立つ拡張メソッド。
// 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 ファイルを問題なく書き込むことができます。
ここにサンプルの例があります。この例では、63MB の xml ファイルを 5 秒未満で生成します。この例では、クラス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();
}
.NET で XML サポートを使用する代わりに、XML ファイルを文字列のように記述することを検討しましたか。
ツールがそれを消費できる唯一の方法だったので、XML に最大 10GB のデータを書き込んでいました。
私はこのような問題を抱えていましたが、私の XML は非常に単純で、TextWriter を使用し、入れ子になった for ループを使用して XML を記述しました。
魅力的で、XML オブジェクトよりもはるかに高速でした。