使用.NET以脚本高效的方式将大型TIF文件加载到字符串中

时间:2015-07-10 22:27:44

标签: c# .net string memory-management bytearray

我已经使用了多年来通过HttpWebRequest POST请求上传XML和TIF文件对的代码。问题是,在大型TIF文件上,它像一群攻击森林的海狸一样通过记忆咀嚼。我今天开始深入研究代码,试图提高内存效率。

现有代码将XML和TIF内容加载到字符串对象中,然后将其转换为字节数组并送入HTTP请求。整个过程涉及许多字符串连接。加载TIF文件并将其转换为这样的字符串对象,其中br2是BinaryReader对象:

System.Text.Encoding.Default.GetString(br2.ReadBytes(tifByteCount))

我现在知道使用Encoding.Default并不明智,但是改变它将需要与客户端一起改变对文件提交的解码,这是另一次。当我进行更改时,我可能会更改为base64编码。总之...

我改变的第一个项目是我的所有字符串连接,因为我认为这会让事情变得迟钝,特别是在处理TIF字符串对象时。我现在正在使用StringBuilder对象并附加所有内容。

然后我搜索了“字节数组到字符串转换”并尝试了我找到的几个不同的结果,包括this onethis one,但两者都使用了与现有代码不同的编码。

然后我使用System.Text.Encoding.Default.Decoder对象将整个TIF文件一次解码为char []数组。这根本没有改善内存,但至少使用了相同的编码。

我今天一直在测试的文件是一个185 MB的TIF文件。在我的开发机器上进行测试时,我的Windows物理内存使用量将从2 GB开始使用,并且很快将升至5 GB以上,然后最大值为5.99 GB并立即锁定,直到调试器自行终止。据我所知,我只是将一个TIF文件实例加载到内存中,所以我无法理解为什么185 MB占用4 GB内存。

无论如何,接下来我尝试在更小的块中加载TIF文件。一次1000个字节。这看起来很有希望。在加载除文件的最后<1000字节之外的所有内容时,它仅使用2 GB内存。在最后一块字节(在这种情况下为928字节),此行charCount = dc.GetCharCount(ba2, x, (int)fileStream2.Length - x)导致内存暂时增加1 GB,后续行chars2 = new Char[(int)fileStream2.Length - x]将内存增加700 MB,并且以下行charsDecodedCount = dc.GetChars(ba2, x, (int)fileStream2.Length - x, chars2, 0)将内存推到最大并锁定了系统。

下面的代码显示了最后尝试的方法 - 上一段中描述的方法。

BinaryReader br2 = new BinaryReader(fileStream2);
byte[] ba2 = br2.ReadBytes((int)fileStream2.Length);
Char[] chars2 = null;

if ((int)fileStream2.Length > 1000)
{
    for (int x = 0; x < (int)fileStream2.Length; x += 1000)
    {
        if (x + 1000 > (int)fileStream2.Length)
        {
            charCount = dc.GetCharCount(ba2, x, (int)fileStream2.Length - x);
            chars2 = new Char[(int)fileStream2.Length - x];
            charsDecodedCount = dc.GetChars(ba2, x, (int)fileStream2.Length - x, chars2, 0);
        }
        else
        {
             charCount = dc.GetCharCount(ba2, x, 1000);
             chars2 = new Char[charCount];
             charsDecodedCount = dc.GetChars(ba2, x, 1000, chars2, 0);
        }

        sbRequest.Append(chars2);
        chars2 = null;
    }
}
else
{
    charCount = dc.GetCharCount(ba2, 0, ba2.Length);
    chars2 = new Char[charCount];
    charsDecodedCount = dc.GetChars(ba2, 0, ba2.Length, chars2, 0);
    sbRequest.Append(chars2);
}

我有一种感觉,我错过了一些相当明显的东西。我很感激任何解决这个问题的建议。我希望能够在不使用4 GB内存的情况下加载185 MB TIF文件!

2 个答案:

答案 0 :(得分:2)

您当前代码中的几个主要问题:

byte[] ba2 = br2.ReadBytes((int)fileStream2.Length);

这会将整个文件读取到内存中。

dc.GetCharCount(...)
dc.GetChars(...)

这些方法使用内部缓冲区,因此它们会增加内存使用量,就像你说的那样。

你不是&#34;在TIF文件中一次加载1000个字节的小字节&#34; 。您将整个文件加载到内存中,并一次解码1000字节的字节。

如果你真的想让你的方法使用尽可能少的内存,我建议只使用流。这是一个例子:

using (var fs = new FileStream("tif file", FileMode.Open))
{
    var request = (HttpWebRequest)WebRequest.Create("address");
    request.Method = "POST";
    request.ContentLength = fs.Length;

    using (Stream postStream = request.GetRequestStream())
    {
        // Write the other contents you wanted to write here
        // ...

        // CopyTo uses a buffer of 4096 bytes by default, so it will
        // only read 4096 bytes into memory at a time.
        fs.CopyTo(postStream);
        postStream.Close(); // Not sure if necessary since we're in a using block
    }

    using (HttpWebResponse response = request.GetResponse()) // might need to cast to HttpWebRequest
    using (Stream responseStream = response.GetResponseStream())
    using (var streamReader = new StreamReader(responseStream))
    {
        string response = Encoding.UTF8.GetString(streamReader.ReadToEnd());
        // Stuff with the response
    }
}

通过在其构造函数中指定FileOptions.SequentialScan,您可以从大型读取文件流中获得更好的性能。此标志&#34;表示要从头到尾依次访问该文件。系统可以使用它作为优化文件缓存的提示。&#34; [1] 您可以找到有关该标志here的详细信息。

using (var fs = new FileStream("tif file", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialSeek))

答案 1 :(得分:0)

使用MIME multipart可以轻松地在单个请求中发布TIFF和XML文档。请参阅下面的示例,其内存使用量以千字节为单位 - 无论文件大小如何:

var content = new MultipartFormDataContent();

var tiffFile = new StreamContent(File.OpenRead("demo.tiff"));
tiffFile.Headers.ContentType = new MediaTypeHeaderValue("image/tiff");
content.Add(tiffFile, "image");

var xml = "<x>foo</x>";
var xmlContent = new StringContent(xml, Encoding.UTF8, "application/xml");
content.Add(xmlContent, "metadata");

var response = (new HttpClient()).PostAsync("http://target/service", content).Result;
response.EnsureSuccessStatusCode();

这会将内容发布到服务器:

POST http://target/service HTTP/1.1
Content-Type: multipart/form-data; boundary="5c3654f8-8e3c-4454-921a-36e0f7761265"
Host: target
Content-Length: 157289445
Expect: 100-continue
Connection: Keep-Alive

--5c3654f8-8e3c-4454-921a-36e0f7761265
Content-Type: image/tiff
Content-Disposition: form-data; name=image

«TIFF Data goes here»
--5c3654f8-8e3c-4454-921a-36e0f7761265
Content-Type: application/xml; charset=utf-8
Content-Disposition: form-data; name=metadata

<x>foo</x>
--5c3654f8-8e3c-4454-921a-36e0f7761265--