将元素复制并附加到XML文档而不缓冲到RAM

正如标题所示,我需要将日志数据附加到XML文件而不缓冲到RAM。 XML文件由LogEntry元素组成,其中包含82个包含数据的子元素。 这些文件可能变得非常大,因为它将构成Windows CE6程序的一部分,我们的内存非常有限。

完成了大量的研究之后,最明显的方法是使用XDocumentLinq to XML来读取现有文档,然后再附加并写出新文档。 在演唱会中使用XmlWriterXmlReader似乎是我追加到文件的最佳方式,但到目前为止我的所有尝试都非常不切实际,并且需要IF语句来指示写入内容以防止重复或少数据元素被写入。

我正在做的事情的本质是:

 //Create an XmlReader to read current WorkLog. using (XmlReader xmlRead = XmlTextReader.Create("WorkLog.xml")) { //Create a XmlWriterSettings and set indent //to true to correctly format the document XmlWriterSettings writerSettings = new XmlWriterSettings(); writerSettings.Indent = true; writerSettings.IndentChars = "\t"; //Create a new XmlWriter to output to using (XmlWriter xmlWriter = XmlWriter.Create("New.xml", writerSettings)) { //Starts the document xmlWriter.WriteStartDocument(); //While the XmlReader is still reading (essentially !EOF) while (xmlRead.Read()) { //FSM to direct writing of OLD Log data to new file switch (xmlRead.NodeType) { case XmlNodeType.Element: //Handle the copying of an element node //Contains many if statements to handle root node & //attributes and to skip nodes that contain text break; case XmlNodeType.Text: //Handle the copying of an text node break; case XmlNodeType.EndElement: //Handle the copying of an End Element node break; } } xmlWriter.WriteEndDocument(); } } 

我相信我可以通过这种方式附加到文件中,但这样做是非常不切实际的 – 有没有人知道我的搜索时间没有出现的任何内存有效方法?

如果需要的话,我很高兴发布我当前的代码来执行此操作 – 但正如我所提到的那样它非常大并且实际上非常讨厌,所以我现在就把它留下来。

你使用XmlReader方法实际上是要走的路……但正如你所说,这是非常不切实际的。

黑客是否合理?

原因是XML有许多您可能遇到的function,需要您从上到下阅读它。 通常, XmlReader处理这些情况,为您提供普通标签等等。 例如,给出以下声明:

    

然后实体book的替换文本是:

 La Peste: Albert Camus, © 1947 Éditions Gallimard. &rights; 

如果您还没有阅读ENTITY标签,则无法对正确的XML进行“翻译”。 也就是说,幸运的是没有很多人使用这种结构,所以可以假设您的XML不使用它们来重写根标签。

也就是说,XML中关闭标记的唯一有效方法是在尾随>之前使用带有可选空格的 > 。 (见http://www.w3.org/TR/2008/REC-xml-20081126/#sec-starttags )。 这基本上意味着您可以跳到最后,读取足够的数据,检查它是否包含结束标记 – 如果包含结束标记,您可以插入自己的代码。 如果没有,请回头再试一次。

讨厌的小编码

最后要注意的是文件的编码。 虽然您可以从流构造XmlTextReader ,但流使用字节,读者会检测编码并开始读取。 幸运的是, XmlTextReaderEncoding作为属性公开,因此您可以使用它。 编码很重要,因为每个字符可能需要不超过1个字节; 特别是当你遇到UTF-16或UTF-32时,这可能是一个问题。 处理此问题的方法是将令牌转换为字节,然后对字节进行匹配。

根=预告片假设

由于我真的不想检查空格和尾随’>’(参见上面的W3C链接),我还假设它是一个有效的XML文件,这意味着每个开始标记也都是关闭的。 这意味着您可以简单地检查 ,从而使匹配过程更容易一些。 ( 注意:您甚至可能只检查文件中的最后一个 ,但我更喜欢我的代码对不正确的XML更强大

把它们放在一起

这里......(我没有测试过它,但它或多或少会起作用)

 public bool FindAppendPoint(Stream stream) { XmlTextReader xr = new XmlTextReader(stream); string rootElement = null; while (xr.Read()) { if (xr.NodeType == XmlNodeType.Element) { rootElement = xr.Name; break; } } if (rootElement == null) { // Well, apparently there's no root... You can start a new file I suppose return false; } else { long start = stream.Position; // the position we're currently reading (end of start tag) long len = stream.Length; long end = Math.Min(start, len - 1024); byte[] endTag = xr.Encoding.GetBytes("= start) { byte[] data = new byte[len - end]; stream.Seek(start, SeekOrigin.Begin); stream.Read(data, 0, data.Length); // FIXME: read returns an int that we should use!!! // Loop backwards till we find the end tag for (int i = data.Length - endTag.Length; i >= 0; --i) { int j; for (j = 0; j < endTag.Length && endTag[j] == data[i + j]; ++j) { } if (j == endTag.Length) { // We found a match! stream.Seek(len - data.Length - i, SeekOrigin.Begin); AppendXml(stream, xr.Encoding) return true; } } // Hmm, we've found  

如果您已了解xml结构,请考虑使用流编写器。 1.将文件作为文件流打开2.将点移动到要替换的标记,例如:,将您的点(位置)移动到“<”3。以正确的xml格式写入您的日志数据并在末尾写下“”写的

“使用文本编辑器处理xml文件”

如果一个hack是合理的,我会转到文件的末尾,倒回结束标记并写入新元素和结束标记。 为了进一步改进,您甚至可以缓存最后一个元素开头的偏移量。

只有使用XmlReader,才能在内存中加载完整的XML。 它也不支持修改,但您可以通过修改从源文档复制XML。 没有其他办法。

将XML解析为文本文档看起来很难。

最好使用类XmlReader / XmlWriter进行解析,并且已经使用您自己的类实现使用Visitor或State GoF模式实现了crud逻辑。 访问者模式将减少if-s的数量,并使您的设计易于扩展。 即使您不想使用XmlReader / XmlWriter解析XML文档,我建议您在这种情况下使用它们。

假设日志文件是这样的(只有两个级别):

  abc1 abc1 abc1  

我使用FileStream来寻找结束并读取结束元素。

 private static void Append(string xmlElement) { const byte lessThan = (byte) '<'; using (FileStream stream = File.Open(@"C:\log.xml", FileMode.OpenOrCreate)) { if (stream.Length == 0) { byte[] rootElement = Encoding.UTF8.GetBytes(""); stream.Write(rootElement, 0, rootElement.Length); } List buffer = new List(); stream.Seek(0, SeekOrigin.End); do { stream.Seek(-1, SeekOrigin.Current); buffer.Insert(0, (byte) stream.ReadByte()); stream.Seek(-1, SeekOrigin.Current); } while (buffer[0] != lessThan); byte[] toAdd = Encoding.UTF8.GetBytes(xmlElement); stream.Write(toAdd, 0, toAdd.Length); stream.Write(buffer.ToArray(), 0, buffer.Count); } }