首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将char[]缓冲区传递给XmlSerializer

将char[]缓冲区传递给XmlSerializer
EN

Stack Overflow用户
提问于 2020-04-13 09:35:28
回答 2查看 207关注 0票数 2

我有一个XML,它存储在char数组中-- char[] --并且在int变量中有数据的内容长度。我需要用XmlSerializer反序列化数据。

出于性能原因,我需要避免分配字符串对象,因为数据通常大于85 in,并且会导致Gen2对象。

是否有任何方法将char[]传递给XmlSerializer而不将其转换为字符串?它接受StreamTextReader,但我找不到从char[]构建char[]的方法。

我想象的是这样的事情(除了C#没有CharArrayStream或CharArrayReader):

代码语言:javascript
复制
public MyEntity DeserializeXmlDocument(char [] buffer, int contentLength) {
    using (var stream = new CharArrayStream(buffer, contentLength))
    {
        return _xmlSerializer.Deserialize(stream) as MyEntity;
    }
}

就像一些更多的信息一样,我们正在分析现有的代码,并且已经确定了一个痛点,所以这不是“过早优化”或"XY问题“的情况。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-04-17 21:13:49

从一个字符数组中读取子类TextReader相当简单。下面是一个ReadOnlyMemory版本,它可以表示stringchar []字符数组的一部分:

代码语言:javascript
复制
public sealed class CharMemoryReader : TextReader
{
    private ReadOnlyMemory<char> chars;
    private int position;

    public CharMemoryReader(ReadOnlyMemory<char> chars)
    {
        this.chars = chars;
        this.position = 0;
    }

    void CheckClosed()
    {
        if (position < 0)
            throw new ObjectDisposedException(null, string.Format("{0} is closed.", ToString()));
    }

    public override void Close() => Dispose(true);

    protected override void Dispose(bool disposing)
    {
        chars = ReadOnlyMemory<char>.Empty;
        position = -1;
        base.Dispose(disposing);
    }

    public override int Peek()
    {
        CheckClosed();
        return position >= chars.Length ? -1 : chars.Span[position];
    }

    public override int Read()
    {
        CheckClosed();
        return position >= chars.Length ? -1 : chars.Span[position++];
    }

    public override int Read(char[] buffer, int index, int count)
    {
        CheckClosed();
        if (buffer == null)
            throw new ArgumentNullException(nameof(buffer));
        if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
        if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
        if (buffer.Length - index < count)
            throw new ArgumentException("buffer.Length - index < count");

        return Read(buffer.AsSpan().Slice(index, count));
    }

    public override int Read(Span<char> buffer)
    {
        CheckClosed();

        var nRead = chars.Length - position;
        if (nRead > 0)
        {
            if (nRead > buffer.Length)
                nRead = buffer.Length;
            chars.Span.Slice(position, nRead).CopyTo(buffer);
            position += nRead;
        }
        return nRead;
    }

    public override string ReadToEnd()
    {
        CheckClosed();
        var s = position == 0 ? chars.ToString() : chars.Slice(position, chars.Length - position).ToString();
        position = chars.Length;
        return s;
    }

    public override string ReadLine()
    {
        CheckClosed();
        var span = chars.Span;
        var i = position;
        for( ; i < span.Length; i++)
        {
            var ch = span[i];
            if (ch == '\r' || ch == '\n')
            {
                var result = span.Slice(position, i - position).ToString();
                position = i + 1;
                if (ch == '\r' && position < span.Length && span[position] == '\n')
                    position++;
                return result;
            }
        }
        if (i > position)
        {
            var result = span.Slice(position, i - position).ToString();
            position = i;
            return result;
        }
        return null;
    }

    public override int ReadBlock(char[] buffer, int index, int count) => Read(buffer, index, count);
    public override int ReadBlock(Span<char> buffer) => Read(buffer);

    public override Task<String> ReadLineAsync() => Task.FromResult(ReadLine());
    public override Task<String> ReadToEndAsync() => Task.FromResult(ReadToEnd());
    public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(ReadBlock(buffer, index, count));
    public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(Read(buffer, index, count));
    public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
        cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(ReadBlock(buffer.Span));
    public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
        cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(Read(buffer.Span)); 
}

然后将其与以下扩展方法之一一起使用:

代码语言:javascript
复制
public static partial class XmlSerializationHelper
{
    public static T LoadFromXml<T>(this char [] xml, int contentLength, XmlSerializer serial = null) => 
        new ReadOnlyMemory<char>(xml, 0, contentLength).LoadFromXml<T>(serial);

    public static T LoadFromXml<T>(this ReadOnlyMemory<char> xml, XmlSerializer serial = null)
    {
        serial = serial ?? new XmlSerializer(typeof(T));
        using (var reader = new CharMemoryReader(xml))
            return (T)serial.Deserialize(reader);
    }
}

例如。

代码语言:javascript
复制
var result = buffer.LoadFromXml<MyEntity>(contentLength, _xmlSerializer);

备注:

  • char []字符数组的内容基本上与没有BOM表的UTF-16编码内存流的内容相同,因此可以创建一个与MemoryStream类似的自定义Stream实现,该实现将每个char表示为两个字节,就像戈尔吉Kő司仪这个答案https://stackoverflow.com/q/1879395/3744182中所做的那样。然而,完全正确地这样做似乎有点棘手,因为正确处理所有async方法似乎并不简单。 这样做之后,XmlReader仍然需要使用“解码”流为一系列字符的StreamReader包装自定义流,从而正确推断流程中的编码(我观察到,有时可能会错误地进行编码,例如当编码声明与实际编码不匹配时)。 为了避免不必要的解码步骤,我选择创建自定义TextReader而不是自定义Stream,因为async实现似乎不那么麻烦。
  • 通过截断将每个char表示为单个字节(例如,(byte)str[i])将损坏包含任何多字节字符的XML。
  • 我还没有对上面的实现进行任何性能优化。

演示小提琴这里

票数 1
EN

Stack Overflow用户

发布于 2020-04-13 10:44:29

我重新编写了@Gy rgy Kőszeg链接到一个类ő的代码。到目前为止,这在我的测试中是可行的:

代码语言:javascript
复制
public class CharArrayStream : Stream
{
    private readonly char[] str;
    private readonly int n;

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => n;
    public override long Position { get; set; } // TODO: bounds check

    public CharArrayStream(char[] str, int n)
    {
        this.str = str;
        this.n = n;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = Length - offset;
                break;
        }

        return Position;
    }

    private byte this[int i] => (byte)str[i];

    public override int Read(byte[] buffer, int offset, int count)
    {
        // TODO: bounds check
        var len = Math.Min(count, Length - Position);
        for (int i = 0; i < len; i++)
        {
            buffer[offset++] = this[(int)(Position++)];
        }
        return (int)len;
    }

    public override int ReadByte() => Position >= Length ? -1 : this[(int)Position++];
    public override void Flush() { }
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override string ToString() => throw new NotSupportedException();
}

我可以这样用它:

代码语言:javascript
复制
public MyEntity DeserializeXmlDocument(char [] buffer, int contentLength) {
    using (var stream = new CharArrayStream(buffer, contentLength))
    {
        return _xmlSerializer.Deserialize(stream) as MyEntity;
    }
}

谢谢,@Gy rgy Kőszeg!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61185023

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档