如何在base64转换后释放内存

时间:2011-03-25 17:36:02

标签: c# memory-leaks out-of-memory

我正在尝试流式传输文件的内容。 该代码适用于较小的文件,但对于较大的文件,我会收到Out of Memory错误。

public void StreamEncode(FileStream inputStream, TextWriter tw)
{
    byte[] base64Block = new byte[BLOCK_SIZE];
    int bytesRead = 0;

    try
    {
        do
        {
            // read one block from the input stream
            bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

            // encode the base64 string
            string base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

            // write the string
            tw.Write(base64String);

        } while (bytesRead == base64Block.Length);
    }
    catch (OutOfMemoryException)
    {
        MessageBox.Show("Error -- Memory used: " + GC.GetTotalMemory(false) + " bytes");
    }
}

我可以隔离问题并观察使用的内存随着循环而增长 问题似乎是对Convert.ToBase64String()的调用。

如何为转换后的字符串释放内存?


从这里编辑下来......这是一个更新。 我还创建了一个关于此的新thread - 抱歉,我想这不是正确的事情。

感谢您的好建议。根据建议,我缩小了用于从文件中读取的缓冲区大小,看起来内存消耗更好,但我仍然看到OOM问题,我看到这个问题的文件大小小到5MB。我可能想要处理十倍大的文件。

我现在的问题似乎是使用TextWriter。

我按如下方式创建了一个请求[进行了一些编辑以缩小代码]:

HttpWebRequest oRequest = (HttpWebRequest)WebRequest.Create(new Uri(strURL));
oRequest.Method = httpMethod;
oRequest.ContentType = "application/atom+xml";
oRequest.Headers["Authorization"] = getAuthHeader();
oRequest.ContentLength = strHead.Length + strTail.Length + longContentSize;
oRequest.SendChunked = true;

using (TextWriter tw = new StreamWriter(oRequest.GetRequestStream()))
{
    tw.Write(strHead);
    using (FileStream fileStream = new FileStream(strPath, FileMode.Open, 
           FileAccess.Read, System.IO.FileShare.ReadWrite))
    {
        StreamEncode(fileStream, tw);
    }
    tw.Write(strTail);
}
.....

调用例程:

public void StreamEncode(FileStream inputStream, TextWriter tw)
{
    // For Base64 there are 4 bytes output for every 3 bytes of input
    byte[] base64Block = new byte[9000];
    int bytesRead = 0;
    string base64String = null;

    do
    {
        // read one block from the input stream
        bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

        // encode the base64 string
        base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

        // write the string
        tw.Write(base64String);


    } while (bytesRead !=0 );

}

由于潜在的大内容,我应该使用TextWriter之外的其他内容吗?能够创建请求的整个有效负载似乎非常方便。

这完全是错误的方法吗?我希望能够支持非常大的文件。

7 个答案:

答案 0 :(得分:4)

如果使用32 KB或更高的BLOCK_SIZE,您将创建85 kB或更多的字符串,这些字符串在大对象堆上分配。短命对象应该存在于常规堆中,而不是大对象堆中,这可能是存储器问题的原因。

此外,我发现代码存在两个潜在问题:

  • base64编码在字符串的末尾使用填充,所以如果你将一个流分成比特并转换为base64字符串,然后将字符串写入一个流,你不会得到一个单个base64流。

  • 检查使用Read方法读取的字节数是否与请求的字节数相同不是检查流结束的正确方法。 Read方法可以在任何时候读取比请求更少的字节,并且检查流结束的正确方法是当方法返回零时。

答案 1 :(得分:1)

请记住,在将数据转换为base64时,结果字符串的长度将增加33%(假设输入大小为3的倍数,这在您的情况下可能是个好主意)。如果BLOCK_SIZE太大,可能没有足够的连续内存来保存生成的base-64字符串。

尝试减少BLOCK_SIZE,以便base-64的每个部分都更小,从而更容易为它分配内存。

但是,如果您使用像StringWriter这样的内存中TextWriter,则可能会遇到同样的问题,因为它无法找到足以容纳内部缓冲区的内存块。如果你正在写一个类似文件的东西,这应该不是问题。

答案 2 :(得分:1)

Wild guess ... HttpWebRequest.AllowWriteStreamBuffering默认为true,根据MSDN“将AllowWriteStreamBuffering设置为true可能会在上传大数据集时导致性能问题,因为数据缓冲区可能会使用所有可用内存”。尝试设置 oRequest.AllowWriteStreamBuffering = false 看看会发生什么。

答案 3 :(得分:0)

尝试将base64String声明拉出循环。如果仍然没有帮助,请尝试在经过多次迭代后调用垃圾收集器。

GC.Collect的(); GC.WaitForPendingFinalizers();

答案 4 :(得分:0)

尝试减小块大小或避免将Convert调用的结果赋值给变量:

bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);
tw.Write(Convert.ToBase64String(base64Block, 0, bytesRead));

答案 5 :(得分:0)

从内存使用的角度看代码看起来不错,但我认为你正在传递基于内存的流的编写器(如MemoryStream)并在那里存储数据会导致OOM异常。

如果BLOCK_SIZE高于86Kb分配将在大对象堆(LOH)上发生,它将改变分配行为,但不应自行引起OOM。

注意:您的结束条件不正确 - 应该是bytesRead!= 0,在genral中,即使剩下更多数据,Read也可以返回比询问更少的字节。据我所知,FileStream也从未这样做过。

答案 6 :(得分:0)

我会先将结果写入临时文件。

using (TextWriter tw = new StreamWriter(oRequest.GetRequestStream()))
{
    tw.Write(strHead);
    var tempPath = Path.GetTempFileName();
    try
    {
        using (var input = File.OpenRead(strPath))
        using (var output = File.Open(
            tempPath, FileMode.Open, FileAccess.ReadWrite))
        {
            StreamEncode(fileStream, output);
            output.Seek(0, SeekOrigin.Begin);
            CopyTo(output, ((StreamWriter)tw).BaseStream);
        }
    }
    finally
    {
        File.Delete(tempPath);
    }
    tw.Write(strTail);
}

public void StreamEncode(Stream inputStream, Stream output)
{
    // For Base64 there are 4 bytes output for every 3 bytes of input
    byte[] base64Block = new byte[9000];
    int bytesRead = 0;
    string base64String = null;

    using (var tw = new StreamWriter(output))
    {
        do
        {
            // read one block from the input stream
            bytesRead = inputStream.Read(base64Block, 0, base64Block.Length);

            // encode the base64 string
            base64String = Convert.ToBase64String(base64Block, 0, bytesRead);

            // write the string
            tw.Write(base64String);

        } while (bytesRead !=0 );
    }

}


static void CopyTo(Stream input, Stream output)
{
    const int length = 10240;
    byte[] buffer = new byte[length];
    int count = 0;

    while ((count = input.Read(buffer, 0, length)) > 0)
        output.Write(buffer, 0, count);
}