从内存流中读取到字符串

59

我正在尝试将一个对象写入 XML 字符串,然后将该字符串保存到数据库中。但首先我需要获取该字符串...

    private static readonly Encoding LocalEncoding = Encoding.UTF8;

    public static string SaveToString<T> (T settings)
    {
        Stream stream = null;
        TextWriter writer = null;
        string settingsString = null;

        try
        {
            stream = new MemoryStream();

            var serializer = new XmlSerializer(typeof(T));

            writer = new StreamWriter(stream, LocalEncoding);

            serializer.Serialize(writer, settings);

            var buffer = new byte[stream.Length];

            stream.Read(buffer, 0, (int)stream.Length);
            
            settingsString = LocalEncoding.GetString(buffer);
        }
        catch (Exception ex)
        {
            // If the action cancels we don't want to throw, just return null.
        }
        finally
        {
            if (stream != null)
                stream.Close();

            if (writer != null)
                writer.Close();
        }

        return settingsString;
    }

看起来这个方法是可行的,流被填充了字节。但是当我试图将其读回缓冲区并存入字符串时...缓冲区被填满了“0”!不确定我在这里做错了什么。


1
可能是重复的问题:如何从MemoryStream获取字符串? - andyp
4个回答

94
如果您检查了stream.Read的结果,您会发现它没有读取任何内容-因为您还没有倒回流(您可以使用stream.Position = 0;来实现)。然而,更简单的方法是直接调用ToArray:
settingsString = LocalEncoding.GetString(stream.ToArray());
(你需要将stream的类型从Stream更改为MemoryStream,但这没关系,因为它在创建它的同一个方法中。)或者更简单的方法是,只需使用StringWriter而不是StreamWriter。如果您想使用UTF-8而不是UTF-16,则需要创建子类,但这很容易。请参见此答案以获取示例。顺便说一句,我担心你只是捕捉了Exception并假设它意味着无害的事情,甚至没有记录任何内容。请注意,使用语句通常比编写显式finally块更清晰。)

谢谢 :)。这是快速的工作。我知道使用块更常见。但我非常喜欢这种扁平化的方式,没有嵌套的使用。这只是一种风格问题。我不知道有一个StringWriter,看起来更容易,但我喜欢指定编码的能力。我可能会添加一个重载,而不是一个新类。 - tigerswithguitars
@tigerswithguitars:你打算如何向StringWriter本身添加重载?新类的重点在于StringWriter始终从其TextWriter.Encoding属性返回UTF-16;额外的类只是覆盖了该属性。 - Jon Skeet
抱歉,我的措辞有些混乱。我将为自己的方法添加一个重载来指定编码。 - tigerswithguitars
@tigerswithguitars:没错,那很好。你仍然需要添加额外的StringWriter类,但那只是在方法内部使用。 - Jon Skeet
我刚刚读完了你的书...今天有人点赞了这个。我已经忘记了你曾经回答过这个问题。太神奇了。另外,“C#深入浅出”是我读过的最好的语言书籍,非常棒。 - tigerswithguitars
@tigerswithguitars:非常高兴你喜欢它 :) - Jon Skeet

44
string result = System.Text.Encoding.UTF8.GetString(fs.ToArray());

2
虽然这段代码示例可能回答了问题,但最好在此处包含一些必要的解释。 - oɔɯǝɹ
1
这里的 stem 是什么?如果是变量,你是如何实例化它的?同样的问题也适用于 fs - Nicolas Raoul
用户错误地使用了stem而不是System。 - Prasanth

14
string result = Encoding.UTF8.GetString((stream as MemoryStream).ToArray());

1
我会质疑在这里使用 as 的意义 - 它并没有保护你免受任何影响。首先,你已经知道流是一个 MemoryStream,即使不是,将 null 传递给 GetString 也会引发异常,因此最好一开始就使用显式转换。 - Simon MᶜKenzie
1
尽管这段代码可能回答了问题,但是提供有关为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - ryanyuyu

8

当流长度非常长时,存在由于大对象堆而导致的内存泄漏风险。即,由stream.ToArray创建的字节缓冲区会在堆内存中创建一个内存流的副本,从而导致保留内存的重复。建议使用StreamReaderTextWriter并分块读取char缓冲区中的流。

在netstandard2.0中,System.IO.StreamReader有一个ReadBlock方法。

您可以使用此方法来读取Stream实例(也适用于MemoryStream实例,因为Stream是MemoryStream的超类型)的实例:

private static string ReadStreamInChunks(Stream stream, int chunkLength)
{
    stream.Seek(0, SeekOrigin.Begin);
    string result;
    using(var textWriter = new StringWriter())
    using (var reader = new StreamReader(stream))
    {
        var readChunk = new char[chunkLength];
        int readChunkLength;
        //do while: is useful for the last iteration in case readChunkLength < chunkLength
        do
        {
            readChunkLength = reader.ReadBlock(readChunk, 0, chunkLength);
            textWriter.Write(readChunk,0,readChunkLength);
        } while (readChunkLength > 0);

        result = textWriter.ToString();
    }

    return result;
}

注意:由于使用MemoryStream可能会导致大内存流实例(memoryStreamInstance.Size >85000字节)的内存泄漏,因此内存泄漏的危险并未完全消除。您可以使用可回收内存流来避免LOH。这是相关的


3
是的 - 使用StreamReader - 但不是因为内存泄漏,而是因为这是最佳的方法! - Graham Laight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接