2013-04-24 58 views
4

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

做完的研究有相当数量是明显的,最常用的方法是使用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(); 
    } 
} 

我相信我可以这样追加到该文件,但它是非常不切实际的这样做 - 没有人知道任何内存有效的方法我的搜寻时间没有变成什么?

如果需要,我很高兴发布我的当前代码来做到这一点 - 但正如我所说,它是非常大的,现在实际上非常讨厌,所以我现在就离开它。

回答

1

你使用XmlReader方法实际上是要走的路......但你也可以说,这是非常不切实际的。

那么黑客有理吗?

之所以这样做,是因为XML有许多可能遇到的功能,需要您从顶部到底部阅读它。通常情况下,XmlReader可以应付这些情况,并为您留下无格式标签等。例如,给定下面的声明:

<!ENTITY % pub "&#xc9;ditions Gallimard" > 
<!ENTITY rights "All rights reserved" > 
<!ENTITY book "La Peste: Albert Camus, &#xA9; 1947 %pub;. &rights;" > 

那么实体book替换文本是:

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

如果您还没有阅读ENTITY标签,这是不可能做到的“翻译”到正确的XML。也就是说,幸运的是没有很多人使用这些类型的构造,所以可以假设你的XML不使用它们来重写根标签。

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

讨厌的小编码

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

根=拖车假设

由于我不觉得自己真的要检查空格和尾随“>”(见上W3C链接),我还以为这是一个有效的XML文件,这意味着每个开标签也被关闭。这意味着您只需检查</root,使匹配过程更容易一些。 (注:你甚至可能只是检查该文件中的最后一个</,但我更喜欢我的代码,以防止不正确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("</" + rootElement); 

     while (end >= 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 with a lot of spaces... oh well 
      // 
      // It's okay to skip back a bit, just have to make sure that we don't skip <0 
      if (end == start) 
      { 
       end = start - 1; // end the loop 
      } 
      else 
      { 
       end = Math.Min(start, end - 1024); 
      } 
     } 

     // Nope, no go. 
     return false; 
    } 
} 
3

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

“的过程用文本编辑器XML文件”

+0

处理XML文件作为文本比阅读整个事情,然后解析它更为高效和显著快,只需添加新数据到最后.... – 2013-04-30 05:22:54

1

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

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

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

+0

Unfor “XPathNavigator”使用'XmlDocument'将整个文件读入内存,这是一个非常令人遗憾的事情,因为起初它看起来就像我之后做的一样。 :-( – Kobunite 2013-04-30 15:26:19

+0

)你有权利,那么你的方式就是XmlReader,它实现了Visitor或State GoF模式 – Regfor 2013-04-30 16:24:21

+0

我已经分别更新了答案 – Regfor 2013-05-01 11:44:26

2

如果黑客行为合理,我会转到文件末尾,倒过去结束标记并写入新元素和结束标记。为了进一步改进,你甚至可以缓存最后一个元素开始的偏移量。

+0

'XmlReader/Writer'只是向前的,所以我显然需要使用'TextReader ''或'StreamReader' - 哪一个更好? – Kobunite 2013-05-01 08:09:42

+0

任何。你有面向文本文件I/O的经验? – 2013-05-01 14:09:03

+0

有一点,我正在寻找使用TextReader - 看起来充满希望。 – Kobunite 2013-05-01 15:47:28

1

认为日志文件是像这样(只有两个级别):

<logs> 
    <Log>abc1</Log> 
    <Log>abc1</Log> 
    <Log>abc1</Log> 
</logs> 

我以前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("<Logs></Logs>"); 
      stream.Write(rootElement, 0, rootElement.Length); 
     } 
     List<byte> buffer = new List<byte>(); 
     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); 
    } 
}