尽可能有效地写入和流式化模板化的Xml

时间:2011-12-02 06:31:14

标签: c# xml performance linq memory-efficient

我正在尝试找到最佳解决方案,以防止我们在构建Xml文档时分配太多内存。我必须用尽可能少的资源构建一个相当大的Xml(Web服务必须能够每秒处理数百个调用)。 Xml本身的结构变化不大,但数据不断变化。 我目前的解决方案是XDocument和XElement(LINQ)。以下是我今天所做的快速示例:

static Stream GetXml(string d1, string d2, string d3)
{
    XElement x = 
        new XElement("myElement",
            new XElement("myOtherElement1", d1),
            new XElement("myOtherElement2", d2),
            new XElement("myOtherElement3", d3));
    // ... more XElement
    // ... return Stream
}

当Xml文档变得太大时,实例化XDocument和数百个XElement会变得非常昂贵,并且每秒的调用次数会下降。 我目前正在考虑创建某种模板引擎,它只是简单地传输字符串(XElement)而不实例化任何对象。你会怎么做?这是正确的做法吗?

static Stream GetXml(string d1, string d2, string d3)
{
    const string xml = @"
<myElement>
  <myOtherElement1>{0}</myOtherElement1>
  <myOtherElement2>{1}</myOtherElement2>
  <myOtherElement3>{2}</myOtherElement3>
</myElement>";

    // What's the best way to {0}, {1}, {2} without allocating 
    // objects and using as little RAM as possible. I cannot 
    // use string.Format since it allocates strings.

    StreamWriter sw = new StreamWriter(stream);
    sw.Write(xml);
}

3 个答案:

答案 0 :(得分:2)

如果您想避免使用正在解析/生成的XML加载内存,请考虑使用http://msdn.microsoft.com/en-us/library/system.xml.linq.xstreamingelement.aspx中讨论的XStreamingElement实现。

答案 1 :(得分:1)

我可以考虑解雇string.Format的唯一原因是你不想立刻将整个XML文档保存在内存中。我写了这个Stream类,当时只能将文档的一小部分保留在内存中。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace StreamTest
{
    public class EnumeratorStream : Stream
    {
        private readonly IEnumerator<string> source;
        private readonly Encoding encoding;

        public Encoding Encoding { get { return encoding; } }

        private byte[] current = new byte[0];
        private int currentPos = 0;

        public EnumeratorStream(IEnumerable<string> source, Encoding encoding)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (encoding == null) encoding = Encoding.Default;

            this.source = source.GetEnumerator();
            this.encoding = encoding;
        }

        private bool MoveNext()
        {
            while (source.MoveNext())
            {
                if (source.Current.Length > 0)
                {
                    current = encoding.GetBytes(source.Current);
                    currentPos = 0;
                    return true;
                }
            }
            current = new byte[0];
            currentPos = 0;
            return false;
        }

        #region Overrides of Stream

        public override bool CanRead { get { return true; } }
        public override bool CanSeek { get { return false; } }
        public override bool CanWrite { get { return false; } }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (buffer == null) throw new ArgumentNullException("buffer");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset");
            if (offset + count > buffer.Length) throw new ArgumentException("Not enough buffer space");

            int totalWritten = 0;
            while (count > 0)
            {
                int remaining = current.Length - currentPos;
                if (remaining == 0 && !MoveNext()) break;
                remaining = current.Length - currentPos;
                if (remaining <= 0) break;
                if (remaining > count)
                {
                    remaining = count;
                }
                Array.Copy(current, currentPos, buffer, offset, remaining);
                offset += remaining;
                count -= remaining;
                totalWritten += remaining;
                currentPos += remaining;
            }
            return totalWritten;
        }

        public override void Flush() { }
        public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
        public override void SetLength(long value) { throw new NotSupportedException(); }
        public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
        public override long Length { get { throw new NotSupportedException(); } }
        public override long Position
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        #endregion
    }
}

示例:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace StreamTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var stream = new EnumeratorStream(Generate("x","y","z"), null);
            var buffer = new byte[256];
            int read;
            while ((read = stream.Read(buffer,0,256)) > 0)
            {
                string s = stream.Encoding.GetString(buffer, 0, read);
                Console.Write(s);
            }
            Console.ReadLine();
        }

        public static IEnumerable<string> Generate(string d1, string d2, string d3)
        {
            yield return "<myElement>";
            yield return "<myOtherElement1>";
            yield return d1;
            yield return "</myOtherElement1>";
            yield return "<myOtherElement2>";
            yield return d2;
            yield return "</myOtherElement2>";
            yield return "<myOtherElement3>";
            yield return d3;
            yield return "</myOtherElement3>";
            yield return "</myElement>";
        }
    }
}

答案 2 :(得分:0)

你可以传递一个StringBuilder。任何重复的字符串(如打开和关闭标记)都会在内存中引用相同的数据,因此您可以节省一些时间。

static Stream GetXml(string d1, string d2, string d3)
{
    StringBuilder xml = new StringBuilder();
    xml.Append("<myElement>");
    AppendElement(xml, d1);
    AppendElement(xml, d2);
    AppendElement(xml, d3);
    xml.Append("</myElement>");

    // Create/return stream
}
static void AppendElement(StringBuilder xml, string value)
{
    xml.Append("<myOtherElement>");
    xml.Append(value);
    xml.Append("</myOtherElement>");
}

为了节省更多,您可以将开始和结束元素拼凑在一起,如下所示:

static Stream GetXml(string d1, string d2, string d3)
{
    StringBuilder xml = new StringBuilder();
    OpenElement(xml, "myElement");
    AppendElement(xml, d1);
    AppendElement(xml, d2);
    AppendElement(xml, d3);
    CloseElement(xml, "myElement");

    // Create/return stream
}
static void AppendElement(StringBuilder xml, string value)
{
    OpenElement(xml, "myOtherElement");
    xml.Append(value);
    CloseElement(xml, "myOtherElement");
}    

static void OpenElement(StringBuilder xml, string elementName)
{
    xml.Append("<");
    xml.Append(elementName);
    xml.Append(">");
}
static void CloseElement(StringBuilder xml, string elementName)
{
    xml.Append("</");
    xml.Append(elementName);
    xml.Append(">");
}