4

正如标题所示,我需要将日志数据附加到 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();
   }
}

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

如果需要,我很乐意发布我当前的代码来执行此操作 - 但正如我所提到的,它非常大,而且目前实际上非常讨厌,所以我暂时将其忽略。

4

5 回答 5

3

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

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

于 2013-04-24T11:57:01.180 回答
2

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

于 2013-04-30T15:45:57.093 回答
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);
    }
}
于 2013-05-02T14:47:54.517 回答
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从流构造一个,但流使用字节并且您的阅读器检测到编码并开始阅读。幸运的是,XmlTextReader公开了Encodingas 属性,因此您可以使用它。编码很重要,因为每个字符可能需要的不仅仅是 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;
    }
}
于 2013-05-06T15:20:47.310 回答
1

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

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

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

于 2013-04-30T14:36:51.007 回答