我的任务是消除应用程序中对大型对象堆的所有(或尽可能多的)分配。最大的违规行为之一是我们的代码计算一个大字符串的MD5哈希。
public static string MD5Hash(this string s)
{
using (MD5CryptoServiceProvider csp = new MD5CryptoServiceProvider())
{
byte[] bytesToHash = Encoding.UTF8.GetBytes(s);
byte[] hashBytes = csp.ComputeHash(bytesToHash);
return Convert.ToBase64String(hashBytes);
}
}
为了示例起见,请注意字符串本身可能已经在LOH中了。我们的目标是防止对堆进行更多的分配。
此外,当前的实现假设UTF8编码(这是一个很大的假设),但实际上的目标是从字符串中生成一个byte[]。
MD5CryptoServiceProvider可以将流作为输入,因此我们可以创建一个方法:
public static string MD5Hash(this Stream stream)
{
using (MD5CryptoServiceProvider csp = new MD5CryptoServiceProvider())
{
return Convert.ToBase64String(csp.ComputeHash(stream));
}
}
这很有希望,因为我们不需要一个字节[]来运行ComputeHash。我们需要一个流对象,它将在ComputeHash请求字节时从字符串中读取字节。
这个颇具争议的问题提供了一种从字符串创建字节数组的方法,而不考虑编码。但是,我们希望避免创建大字节数组。
这个问题提供了一种通过将字符串读取到MemoryStream中来从字符串创建流的方法,但在内部也只是分配一个大字节[]数组。
两者都没有真正奏效。
那么,如何避免分配大字节[]呢?是否有一个Stream类将在读取字节时从另一个流(或读取器)读取?
如果您不关心编码,那么可以做一件事来防止任何进一步的缓冲区分配,那就是使用一些不安全的代码。也就是说,获取字符串的原始字节,将UnmanagedMemoryStream
的实例封装在它周围,并将其提供给MD5加密计算。
这样的东西:
public static string MD5Hash(this string s)
{
using (MD5CryptoServiceProvider csp = new MD5CryptoServiceProvider())
{
unsafe
{
fixed (char* input = s)
{
using (var stream = new UnmanagedMemoryStream((byte*)input, sizeof(char) * s.Length))
return Convert.ToBase64String(csp.ComputeHash(stream));
}
}
}
}
您可以实现由字符串支持的自己的流。
请注意,基本上您只需要实现Read
和Write
,相应地使用文档(但只需在Write
上抛出一个NotSupportedException
,因为您不应该写入此流):
当您实现Stream的派生类时,必须提供Read和Write方法的实现。异步方法ReadAsync、WriteAsync和CopyToAsync在其实现中使用同步方法Read和Write。
您可能还想实现ReadByte
:
ReadByte和WriteByte的默认实现创建一个新的单元素字节数组,然后调用您的Read和Write 实现
来源:https://msdn.microsoft.com/pt-br/library/system.io.stream%28v=vs.110%29.aspx