String to Stream而不将整个内容重新分配为byte []

时间:2017-02-04 21:48:50

标签: .net string stream allocation

我知道我可以使用简单Stream + string组合从MemoryStream创建StreamWriter

MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(value);
writer.Flush();
stream.Position = 0;

或在编码上使用GetBytes

new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""))

但是,这两个解决方案最终都会将整个字符串重新分配为byte[]。对于长串而言,这是一个大问题。

有没有办法直接在Stream上获得string而无需重新分配整个内容?类似于如何将MemoryStream包裹在现有byte[]上。

1 个答案:

答案 0 :(得分:1)

  

有没有办法直接在字符串上获取Stream而无需重新分配整个内容?类似于如何将MemoryStream包装在现有的byte []上。

创建一个自定义流类来包装它。以下是非常粗糙的,但它的表现比我预期的好很多。我只是把它鞭打了,所以它没有太多的测试(即:它适用于我的一次测试运行),但它应该足以显示这个概念。

编辑:我修改了代码,以解决在原始代码无法从多字节字符中读取单个字节的注释中出现的问题。我还添加了指定可选编码器的功能,用于将字符转换为字节。如果未提供编码器,将使用UTF-8。

使用StreamReader对代码进行了最低限度的测试,但不应将其视为可供生产使用。

Imports System.Text

Public Class StringStream : Inherits IO.Stream
    Private bm As BufferManager

    ''' <summary>
    ''' Creates a non seekable stream from a System.String
    ''' </summary>
    ''' <param name="source"></param>
    ''' <param name="encoding">Default UTF-8</param>
    ''' <remarks></remarks>
    Public Sub New(source As String, Optional encoding As System.Text.Encoding = Nothing)
        Me.bm = New BufferManager(source, encoding)
    End Sub

    Public Overrides ReadOnly Property CanRead As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property CanSeek As Boolean
        Get
            Return False
        End Get
    End Property

    Public Overrides ReadOnly Property CanWrite As Boolean
        Get
            Return False
        End Get
    End Property

    Public ReadOnly Property Encoding As System.Text.Encoding
        Get
            Return bm.Encoding
        End Get
    End Property

    Public Overrides Sub Flush()
    End Sub

    Public Overrides ReadOnly Property Length As Long
        Get
            Return 1 'Me.source.Length
        End Get
    End Property

    Public Overrides Property Position As Long
        Get
            Return Me.bm.Position
        End Get
        Set(value As Long)
            ' seek not supported
        End Set
    End Property

    Public Overrides Function ReadByte() As Integer
        ' Ref: https://msdn.microsoft.com/en-us/library/system.io.stream.readbyte(v=vs.110).aspx
        ' Reads a byte from the stream and advances the position within the stream by one byte, 
        ' or returns -1 if at the end of the stream.
        Dim ret As Int32 = -1
        Dim b As Byte
        If Me.bm.GetByte(b) Then ret = b
        Return ret
    End Function

    Public Overrides Function Read(buffer() As Byte, offset As Integer, count As Integer) As Integer
    ' ref: https://msdn.microsoft.com/en-us/library/system.io.stream.read(v=vs.110).aspx
    ' Return Value: The total number of bytes read into the buffer. 
    ' This can be less than the number of bytes requested if that many bytes are not currently available,
    ' or zero (0) if the end of the stream has been reached.
        Dim maxReturnedCount As Int32 = Math.Min(buffer.Length, count)
        Dim returnCount As Int32
        For i As Int32 = 0 To maxReturnedCount - 1
            If Me.bm.GetByte(buffer(i)) Then
                returnCount += 1
            Else
                Exit For
            End If
        Next
        Return returnCount

    End Function

    Public Overrides Function Seek(offset As Long, origin As IO.SeekOrigin) As Long
        Return -1
    End Function

    Public Overrides Sub SetLength(value As Long)
    End Sub

    Public Overrides Sub Write(buffer() As Byte, offset As Integer, count As Integer)
    End Sub

    Private Class BufferManager
        Private buffer As Byte()
        Private bufferPosition As Int32
        Private source As String
        Private positionInSource As Int32
        Private _encoding As System.Text.Encoding
        Private numBytesInbuffer As Int32
        Private readPosition As Int32

        Public Sub New(source As String, encoding As System.Text.Encoding)
            If encoding Is Nothing Then
                encoding = System.Text.Encoding.UTF8
            End If
            Me.source = source
            Me._encoding = encoding
            buffer = New Byte(0 To encoding.GetMaxByteCount(1) - 1) {}
        End Sub

        Public ReadOnly Property HasBytes As Boolean
            Get
                Return (numBytesInbuffer > 0) OrElse LoadCharToBuffer()
            End Get
        End Property

        Public ReadOnly Property Encoding As System.Text.Encoding
            Get
                Return Me._encoding
            End Get
        End Property

        Public ReadOnly Property Position As Int32
            Get
                Return Me.readPosition
            End Get
        End Property
        Private Function LoadCharToBuffer() As Boolean
            Dim ret As Boolean
            If positionInSource < Me.source.Length Then
                Me.numBytesInbuffer = Me._encoding.GetBytes(source, positionInSource, 1, buffer, 0)
                Me.positionInSource += 1
                Me.bufferPosition = 0
                ret = True
            End If
            Return ret
        End Function

        Public Function GetByte(ByRef value As Byte) As Boolean
            Dim ret As Boolean = Me.HasBytes
            If ret Then
                value = buffer(bufferPosition)
                Me.bufferPosition += 1
                Me.numBytesInbuffer -= 1
                Me.readPosition += 1
            End If
            Return ret
        End Function

    End Class

End Class