写入然后从MemoryStream中读取

Gaz*_*Gaz 60 c#

我正在使用DataContractJsonSerializer,它喜欢输出到Stream.我想要对串行器的输出进行顶部和尾部处理,因此我使用StreamWriter交替写入我需要的额外位.

var ser = new DataContractJsonSerializer(typeof (TValue));

using (var stream = new MemoryStream())
{   
    using (var sw = new StreamWriter(stream))
    {
        sw.Write("{");

        foreach (var kvp in keysAndValues)
        {
            sw.Write("'{0}':", kvp.Key);
            ser.WriteObject(stream, kvp.Value);
        }

        sw.Write("}");
    }

    using (var streamReader = new StreamReader(stream))
    {
        return streamReader.ReadToEnd();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我这样做时,我得到一个ArgumentException"流不可读".

我可能在这里做错了所以所有答案都欢迎.谢谢.

Jon*_*eet 114

三件事:

  • 不要关闭StreamWriter.这将关闭MemoryStream.你确实需要刷新编写器.
  • 在读取之前重置流的位置.
  • 如果您要直接写入流,则需要先刷新writer.

所以:

using (var stream = new MemoryStream())
{
    var sw = new StreamWriter(stream);
    sw.Write("{");

    foreach (var kvp in keysAndValues)
    {
        sw.Write("'{0}':", kvp.Key);
        sw.Flush();
        ser.WriteObject(stream, kvp.Value);
    }    
    sw.Write("}");            
    sw.Flush();
    stream.Position = 0;

    using (var streamReader = new StreamReader(stream))
    {
        return streamReader.ReadToEnd();
    }
}
Run Code Online (Sandbox Code Playgroud)

还有另一个更简单的选择.阅读时您正在使用流进行的所有操作都将其转换为字符串.你可以更简单地做到这一点:

return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int) stream.Length);
Run Code Online (Sandbox Code Playgroud)

不幸的是,MemoryStream.Length如果流已经关闭,则抛出,因此您可能想要调用StreamWriter不关闭底层流的构造函数,或者只是不要关闭StreamWriter.

我很担心你直接写到流 - 什么是ser?它是XML序列化程序还是二进制序列化程序?如果它是二进制的,那么你的模型有些缺陷 - 你不应该混淆二进制和文本数据而不要非常小心.如果它是XML,您可能会发现在字符串中间最终会出现字节顺序标记,这可能会有问题.

  • 这是它对我有用的唯一方式.我无法使Encoding.UTF8.GetString()方法起作用.它说它不适用于封闭的流. (3认同)
  • 没想到只是抢缓冲区-不错的一个 (2认同)
  • 'sw.Flush();`的阿门.我疯狂地想弄清楚为什么MemoryStream没有数据写入它! (2认同)
  • 在 .net 4.5 中,有一个重载的 StreamWriter 构造函数,它将 StreamWriter 配置为在写入器被释放时使流保持打开状态。这将允许您保留 using 块,并避免刷新调用。https://msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx (2认同)

Shu*_*oUk 7

将内存流位置设置为开头可能会有所帮助.

 stream.Position = 0; 
Run Code Online (Sandbox Code Playgroud)

但核心问题是StreamWriter在关闭时关闭了你的内存流.

简单地冲洗你在哪里结束的使用块,并仅处置它是流压脚提升您已经阅读了数据从内存流将解决这个给你的.

您可能还想考虑使用StringWriter而不是......

using (var writer = new StringWriter())
{
    using (var sw = new StreamWriter(stream))
    {
        sw.Write("{");

        foreach (var kvp in keysAndValues)
        {
            sw.Write("'{0}':", kvp.Key);
            ser.WriteObject(writer, kvp.Value);
        }
        sw.Write("}");
    }

    return writer.ToString();
}
Run Code Online (Sandbox Code Playgroud)

这将要求您的序列化WriteObject调用可以接受TextWriter而不是Stream.


Sim*_*les 5

在关闭后访问 MemoryStream 的内容,请使用ToArray()GetBuffer()方法。以下代码演示了如何以 UTF8 编码字符串的形式获取内存缓冲区的内容。

byte[] buff = stream.ToArray(); 
return Encoding.UTF8.GetString(buff,0,buff.Length);
Run Code Online (Sandbox Code Playgroud)

注意:ToArray()GetBuffer()因为ToArray()返回流的确切长度而不是缓冲区大小(可能大于流内容)而使用更简单。 ToArray()复制字节。

注意:GetBuffer()比 性能更高ToArray(),因为它不会复制字节。您确实需要通过考虑流长度而不是缓冲区大小来处理缓冲区末尾可能存在的未定义尾随字节。GetBuffer()如果流大小大于 80000 字节,强烈建议使用,因为 ToArray 副本将分配在大对象堆上,在那里它的生命周期可能会出现问题。

也可以按如下方式克隆原始 MemoryStream,以方便通过 StreamReader 访问它,例如

using (MemoryStream readStream = new MemoryStream(stream.ToArray()))
{
...
}
Run Code Online (Sandbox Code Playgroud)

如果可能,理想的解决方案是在关闭之前访问原始 MemoryStream。

  • “注意:ToArray() 比 GetBuffer() 更好,因为 ToArray() 返回流的确切长度,而不是缓冲区大小(可能大于流内容)。” 但你为什么要关心?将过大的数组传递给“Encoding.GetString”没有任何缺点——我们确切地知道告诉它解码的字节数。`GetBuffer` *不* 创建副本的事实正是您使用它的原因 - 它避免了创建 `ToArray` 创建的无意义副本。 (2认同)