【问题标题】:Fastest way to add new node to end of an xml?将新节点添加到 xml 末尾的最快方法?
【发布时间】:2010-10-25 08:15:27
【问题描述】:

我有一个大 xml 文件(大约 10 MB),结构简单:

<Errors>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
</Errors>

我的需要是在 标签之前的末尾添加一个新节点 。在 .net 中实现这一目标的最快方法是什么?

【问题讨论】:

  • 你是怎么写这个的?使用 DOM?萨克斯?直接写? :-P 我个人更喜欢 DOM 方法;您只需访问错误节点并调用 appendChild()。
  • 请为这种情况定义“最快”;您的意思是“执行速度最快”还是“开发速度最快”?

标签: c# .net xml


【解决方案1】:

您需要使用 XML 包含技术。

你的error.xml(不会改变,只是一个存根。被XML解析器用来读取):

<?xml version="1.0"?>
<!DOCTYPE logfile [
<!ENTITY logrows    
 SYSTEM "errorrows.txt">
]>
<Errors>
&logrows;
</Errors>

您的 errorrows.txt 文件(更改,xml 解析器不理解它):

<Error>....</Error>
<Error>....</Error>
<Error>....</Error>

然后,向 errorrows.txt 添加一个条目:

using (StreamWriter sw = File.AppendText("logerrors.txt"))
{
    XmlTextWriter xtw = new XmlTextWriter(sw);

    xtw.WriteStartElement("Error");
    // ... write error messge here
    xtw.Close();
}

或者您甚至可以使用 .NET 3.5 XElement,并将文本附加到 StreamWriter

using (StreamWriter sw = File.AppendText("logerrors.txt"))
{
    XElement element = new XElement("Error");
    // ... write error messge here
    sw.WriteLine(element.ToString());
}

另见Microsoft's article Efficient Techniques for Modifying Large XML Files

【讨论】:

    【解决方案2】:

    首先,我将取消 System.Xml.XmlDocument 的资格,因为it is a DOM 需要在内存中解析和构建整个树,然后才能将其附加到。这意味着您的 10 MB 文本将超过 10 MB 的内存。这意味着它是“内存密集型”和“耗时”的。

    其次,我会取消 System.Xml.XmlReader 的资格,因为它首先是requires parsing the entire file,然后才能到达可以附加到它的时间点。您必须将 XmlReader 复制到 XmlWriter 中,因为您无法修改它。这需要先在内存中复制您的 XML,然后才能附加到它。

    XmlDocument 和 XmlReader 更快的解决方案是字符串操作(它有自己的内存问题):

    string xml = @"<Errors><error />...<error /></Errors>";
    int idx = xml.LastIndexOf("</Errors>");
    
    xml = xml.Substring(0, idx) + "<error>new error</error></Errors>";
    

    剪掉结束标签,添加新的错误,然后重新添加结束标签。

    我想你可能会为此发疯,将文件截断 9 个字符并附加到它上面。不必读取文件并让操作系统优化页面加载(只需加载最后一个块或其他内容)。

    System.IO.FileStream fs = System.IO.File.Open("log.xml", System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite);
    fs.Seek(-("</Errors>".Length), System.IO.SeekOrigin.End);
    fs.Write("<error>new error</error></Errors>");
    fs.Close();
    

    如果您的文件为空或仅包含“”,这将遇到问题,这两种情况都可以通过检查长度轻松处理。

    【讨论】:

    • OpenText() 打开一个文件进行读取并返回一个 StreamReader。
    • 太棒了!你解决了一个非常大的问题,我不明白为什么这个答案没有超过 1k
    • 谢谢,Colin -- Seek 技术对于我经常遇到的一个案例来说是一个非常有用、简单的解决方案。
    【解决方案3】:

    最快的方法可能是直接访问文件。

    using (StreamWriter file = File.AppendText("my.log"))
    {
        file.BaseStream.Seek(-"</Errors>".Length, SeekOrigin.End);
        file.Write("   <Error>New error message.</Error></Errors>");
    }
    

    但是您会丢失所有不错的 XML 功能,并且可能很容易损坏文件。

    【讨论】:

    • 这也是我的建议。
    • 我正在尝试此操作,但得到“无法向后搜索以覆盖以前存在于以附加模式打开的文件中的数据。” .Seek 行上的错误。示例正确吗?
    • 不,示例不正确,但您需要做的就是将 'File.AppendText(...)' 替换为 'new StreamWriter(File.Open(filePath, FileMode .Open, FileAccess.Write)'
    【解决方案4】:

    我会使用 XmlDocument 或 XDocument 来加载您的文件,然后进行相应的操作。

    然后我会考虑在内存中缓存此 XmlDocument 的可能性,以便您可以快速访问该文件。

    你需要速度做什么?您是否已经遇到了性能瓶颈,或者您正在期待一个?

    【讨论】:

    • XmlDocument 是一种 DOM 模型,它比 XmlReader 中的 SAX 慢。 XmlDocument 需要将整个 10 MB 内存表示为对象(因此总共超过 10 MB)。 XmlReader 会更快(我相当肯定 XmlDocument 是基于 XmlReader 构建的),但您仍然必须解析整个文档。如果 Ramesh 所做的只是附加到日志文件(似乎是这种情况),那么对我来说,这两者都不符合“快速”的条件。
    • 我完全同意,但我总是避免编写带有文本附加的 XML。我的答案是找出他是否可以将文档加载到内存中然后写入。那会很快。然后是另一个偶尔将 XmlDocument 写入文件的进程。这一切都取决于场景。
    【解决方案5】:

    您的 XML 文件在代码中是如何表示的?您使用 System.XML 类吗?在这种情况下,您可以使用 XMLDocument.AppendChild。

    【讨论】:

      【解决方案6】:

      试试这个:

              var doc = new XmlDocument();
              doc.LoadXml("<Errors><error>This is my first error</error></Errors>");
      
              XmlNode root = doc.DocumentElement;
      
              //Create a new node.
              XmlElement elem = doc.CreateElement("error");
              elem.InnerText = "This is my error";
      
              //Add the node to the document.
              if (root != null) root.AppendChild(elem);
      
              doc.Save(Console.Out);
              Console.ReadLine();
      

      【讨论】:

      • 这绝对不是最快的方法。
      【解决方案7】:

      下面是用 C 做的,.NET 应该是类似的。

      游戏就是简单的跳转到文件的末尾,跳过标签,追加新的错误行,写一个新的标签。

      #include <stdio.h>
      #include <string.h>
      #include <errno.h>
      
      int main(int argc, char** argv) {
              FILE *f;
      
              // Open the file
              f = fopen("log.xml", "r+");
      
              // Small buffer to determine length of \n (1 on Unix, 2 on PC)
              // You could always simply hard code this if you don't plan on 
              // porting to Unix.
              char nlbuf[10];
              sprintf(nlbuf, "\n");
      
              // How long is our end tag?
              long offset = strlen("</Errors>");
      
              // Add in an \n char.
              offset += strlen(nlbuf);
      
              // Seek to the END OF FILE, and then GO BACK the end tag and newline
              // so we use a NEGATIVE offset.
              fseek(f, offset * -1, SEEK_END);
      
              // Print out your new error line
              fprintf(f, "<Error>New error line</Error>\n");
      
              // Print out new ending tag.
              fprintf(f, "</Errors>\n");
      
              // Close and you're done
              fclose(f);
      }
      

      【讨论】:

        【解决方案8】:

        最快的方法可能是使用XmlReader 读取文件,然后使用XmlWriter 简单地将每个读取节点复制到一个新流中,那么您只需要在继续“读取和复制”循环之前输出额外的&lt;Error&gt; 元素。这种方式不可避免地比将整个文档读入 DOM(XmlDocument 类)更难,但对于大型 XML 文件,要快得多。诚然,使用 StreamReader/StreamWriter 会更快一些,但在代码中使用起来非常糟糕。

        【讨论】:

          【解决方案9】:

          使用基于字符串的技术(例如查找文件末尾,然后向后移动结束标记的长度)容易受到文档结构中意外但完全合法的变化的影响。

          文档可以以任意数量的空格结尾,以选择您最可能遇到的问题。它也可以以任意数量的 cmets 或处理指令结束。如果顶级元素没有命名为Error,会发生什么?

          这是一种使用字符串操作完全无法检测到的情况:

          <Error xmlns="not_your_namespace">
             ...
          </Error>
          

          如果您使用XmlReader 来处理 XML,虽然它可能不如寻找 EOF 快,但它还允许您处理所有这些可能的异常情况。

          【讨论】:

          • 他提供的文件看起来像一个日志文件,我认为他正处于附加到它的速度越来越慢的地步,因此他提出了问题。可以说我认为日志格式完全在他的控制之下。
          • 做出这些假设通常是非常好的。不过,我不得不修复很多开发人员猜错的代码。在大多数情况下,开发人员甚至都不知道他在猜测。
          【解决方案10】:

          我尝试使用其他答案建议的代码,但遇到了一个问题,有时在我的字符串上调用 .length 与字符串的字节数不同,因此我会不一致地丢失字符。我修改它以获取字节数。

          var endTag = "</Errors>";
          var nodeText = GetNodeText();
          using (FileStream file = File.Open("my.log", FileMode.Open, FileAccess.ReadWrite))
          {
              file.BaseStream.Seek(-(Encoding.UTF8.GetByteCount(endTag)), SeekOrigin.End);
              fileStream.Write(Encoding.UTF8.GetBytes(nodeText), 0, Encoding.UTF8.GetByteCount(nodeText));
              fileStream.Write(Encoding.UTF8.GetBytes(endTag), 0, Encoding.UTF8.GetByteCount(endTag));
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-06-14
            • 2014-02-02
            • 2013-11-13
            • 1970-01-01
            相关资源
            最近更新 更多