Ran dans une exigence similaire dans mon travail. Mon meilleur effort (intuitif, facilité de mise en œuvre, relativement performant) est le suivant. J'écris essentiellement avec un XmlWriter
, surveillant le flux sous-jacent. Quand il dépasse ma limite de taille de fichier, je complète le fragment Xml actuel, enregistre le fichier, ferme le flux.
Ensuite, lors d'un second passage, je charge le DOM complet en mémoire et je supprime itérativement les noeuds et enregistre le document jusqu'à ce qu'il soit de taille acceptable.
Par exemple
// 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");
}
Il existe des moyens d'améliorer ce; une possibilité si la mémoire est une contrainte serait de réécrire le bit itératif pour prendre le nombre de nœuds réellement écrits en premier passage, puis réécrire le fichier moins un élément, et continuer jusqu'à ce que le document entier ait la taille désirée.
Cette dernière recommandation peut être la route à suivre, en particulier si vous avez déjà besoin de suivre les éléments écrits pour reprendre l'écriture dans un autre fichier.
Espérons que cela aide!
EDIT
Bien intuitive et plus facile à mettre en œuvre, je l'ai senti la peine enquête sur l'optimisation mentionnée ci-dessus. C'est ce que j'ai.
Une méthode d'extension qui permet de noeuds ancêtres d'écriture (par exemple des noeuds de conteneurs, et tous les autres types de balisage),
// 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;
}
}
et un procédé qui va effectuer la coupe (pas une méthode d'extension, depuis étendant l'une quelconque des les types de paramètres seraient un peu ambigus).
// 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);
}
Si votre Xml contient la mise en forme des espaces, les espaces blancs entre le dernier nœud victime restant et balise de fermeture de l'élément conteneur est perdu. Cela peut être atténué en modifiant la clause skip (en déplaçant le saut de message d'instruction j++
), mais vous vous retrouvez avec des espaces supplémentaires. La solution présentée ci-dessus génère une réplique de taille minimale du fichier source.
Qu'allez-vous faire avec le reste? À quoi ressembleraient vos fichiers de sortie? –
Quels sont les problèmes avec jusqu'à diviser en 50 fichiers? –