【问题标题】:Writing to then reading from a MemoryStream写入然后从 MemoryStream 读取
【发布时间】:2009-08-05 10:43:03
【问题描述】:

我正在使用DataContractJsonSerializer,它喜欢输出到流。我想对序列化器的输出进行顶部和尾部,所以我使用 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();
    }
}

当我这样做时,我得到一个ArgumentException“流不可读”。

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

【问题讨论】:

    标签: c#


    【解决方案1】:

    三件事:

    • 不要关闭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();
        }
    }
    

    不过,还有另一种更简单的选择。读取时您对流所做的所有操作都是将其转换为字符串。你可以更简单地做到这一点:

    return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int) stream.Length);
    

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

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

    【讨论】:

    • 没想到只是抓取缓冲区 - 不错
    • 这听起来像你没有得到正确的冲洗。你在 Jon 的例子中添加了所有的刷新调用吗?
    • 阿门sw.Flush();。我要疯了,试图弄清楚为什么 MemoryStream 没有数据,尽管写入它!
    • 这是唯一对我有用的方法。我无法让 Encoding.UTF8.GetString() 方法工作。它说它不适用于封闭的流。
    • 在 .net 4.5 中有一个重载的 StreamWriter 构造函数,它将 StreamWriter 配置为在释放 writer 时保持流打开。这将允许您保留 using 块,并避免刷新调用。 msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx
    【解决方案2】:

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

     stream.Position = 0; 
    

    但核心问题是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();
    }
    

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

    【讨论】:

    • 是的,对不起,我现在添加了 ser 的声明。它只接受一个 XmlWriter,这并没有真正帮助我,因为我不能只添加'{'s 和其他东西。我猜它在下面使用了 XmlSerializer,但我现在不想考虑这个问题。设置位置无效。
    【解决方案3】:

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

    byte[] buff = stream.ToArray(); 
    return Encoding.UTF8.GetString(buff,0,buff.Length);
    

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

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

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

    using (MemoryStream readStream = new MemoryStream(stream.ToArray()))
    {
    ...
    }
    

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

    【讨论】:

    • "注意:ToArray() 比 GetBuffer() 好,因为 ToArray() 返回流的确切长度,而不是缓冲区大小(可能大于流内容)。但你为什么在乎?将一个过大的数组传递给Encoding.GetString 没有任何缺点——我们确切地知道要告诉它解码的字节数。 GetBuffer 创建副本的事实正是您使用它的原因 - 它避免了创建 ToArray 创建的无意义的副本。
    • 好点@JonSkeet 我将更新我的答案以反映性能考虑。如果有人感兴趣,这里有一个关于 MemoryStream.GetBuffer 优点的堆栈交换问题stackoverflow.com/questions/13053739/…
    【解决方案4】:

    只是一个疯狂的猜测:也许您需要刷新流写入器?系统可能会看到有“待处理”的写入。通过刷新,您可以确定流包含所有写入的字符并且是可读的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-25
      • 1970-01-01
      • 2020-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多