从内存中读取XML <byte>

时间:2019-06-05 13:37:28

标签: c# .net .net-core

我有一个Memory<byte>,它保存XML元素的二进制数据。我想将XML元素读入XElement对象,但这似乎比乍一看容易。使用byte[]时,我会这样做:

public static XElement GetXElementFromByteArray(byte[] buffer)
{
  using var stream = new MemoryStream(buffer);
  using var xmlReader = XmlReader.Create(stream);

  return XElement.Load(xmlReader);
}

不幸的是,我无法从StreamMemory<T>对象创建Span<T>对象,并且XmlReader需要一个流对象。当然,我可以将Memory<byte>转换为字节数组,但这将复制数据,而我的优化目标是减少集合。

2 个答案:

答案 0 :(得分:1)

实际上并没有可以通过Stream方便地使用的预先罐头XmlReader实现(或Memory<byte>实现)。如果目的是使用MemoryStream,那么最好的办法是 try 并将其作为数组获取,而如果不是,则强制使用合并副本:

ArraySegment<byte> segment = default;
bool leased = false;
try
{
    if (!MemoryMarshal.TryGetArray<byte>(memory, out segment))
    {
        var arr = ArrayPool<byte>.Shared.Rent(memory.Length);
        memory.CopyTo(arr);
        segment = new ArraySegment<byte>(arr, 0, memory.Length);
        leased = true;
    }
    using (var ms = new MemoryStream(segment.Array, segment.Offset, segment.Count))
    {
        // ... your usage goes here!
        using (var xmlReader = XmlReader.Create(ms))
        {
            return XElement.Load(xmlReader);
        }
    }
}
finally
{
    if (leased) ArrayPool<byte>.Shared.Return(segment.Array);
}

另一种方法是创建一个新的Stream实现,该实现可与Memory<byte>配合使用。这是很多工作。

答案 1 :(得分:1)

我想出了一个自定义流,该流似乎有效,但是我想知道是否存在“官方”实现。我知道这仅适用于Memory<byte>而不适用于通用类型,但是通常以1个字节为单位完成内存访问。

这是我的实现方式

public class ReadonlyMemByteStream : Stream
{
    private readonly ReadOnlyMemory<byte> _buffer;
    private int _offset;

    public ReadonlyMemByteStream(ReadOnlyMemory<byte> buffer)
    {
        _buffer = buffer;
        _offset = 0;
    }

    public override void Flush()
    {
        // NOP
    }

    public override int ReadByte()
    {
        return _buffer.Span[_offset++];
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        var bufferLength = _buffer.Length - _offset;
        if (count > bufferLength)
            count = bufferLength;
        _buffer.Span.Slice(_offset).CopyTo(new Span<byte>(buffer, offset, count));
        _offset += count;
        return count;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                _offset = (int)offset;
                break;
            case SeekOrigin.Current:
                _offset += (int)offset;
                break;
            case SeekOrigin.End:
                _offset = _buffer.Length + (int)offset;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
        }
        return _offset;
    }

    public override void SetLength(long value) => throw new NotSupportedException();

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;

    public override long Length => _buffer.Length;

    public override long Position
    {
        get => _offset;
        set => _offset = (int)value;
    }
}

public class MemByteStream : ReadonlyMemByteStream
{
    private readonly Memory<byte> _buffer;

    public MemByteStream(Memory<byte> buffer) : base(buffer)
    {
        _buffer = buffer;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        new Span<byte>(buffer, offset, count).CopyTo(_buffer.Span.Slice((int)Position));
        Position += count;
    }

    public override bool CanWrite => true;
}