我已经知道C#/ .NET中的MemoryStream上的GetBuffer()
必须小心使用,因为,正如文档描述here,最后可能有未使用的字节,所以你必须确保只查看缓冲区中的第一个MemoryStream.Length字节。
但是昨天我遇到了一个案例,其中缓冲区开头的字节是垃圾!实际上,如果您使用像反射器这样的工具并查看ToArray()
,您可以看到:
public virtual byte[] ToArray()
{
byte[] dst = new byte[this._length - this._origin];
Buffer.InternalBlockCopy(this._buffer, this._origin, dst, 0,
this._length - this._origin);
return dst;
}
所以要对GetBuffer()
返回的缓冲区做任何事情,你真的需要知道_origin。唯一的问题是_origin是私有的,没有办法实现它......
所以我的问题是 - GetBuffer()
对MemoryStream()
有什么用处,而没有对MemoryStream的构造方式有什么先验知识(这是什么设置_origin)?
(正是这个构造函数,只有这个构造函数,设置原点 - 当你想要一个字节数组的MemoryStream从字节数组中的特定索引开始时:
public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
)
答案 0 :(得分:15)
答案在GetBuffer() MSDN doc,您可能错过了。
创建MemoryStream
而不提供字节数组(byte[]
)时:
它创建了一个初始化为零的可扩展容量。
换句话说,当在流上进行byte[]
调用时,MemoryStream将引用具有适当大小的Write
。
因此,使用GetBuffer()
,您可以直接访问基础数组并读取它。
当您遇到不知道其大小的流时,可能非常有用。如果收到的信息流通常非常大,那么拨打GetBuffer()
比调用ToArray()
并快速复制数据的速度要快得多,见下文。
要仅获取缓冲区中的数据,请使用ToArray方法; 但是,ToArray会在内存中创建数据副本。
我想知道你可能在一开始就调用GetBuffer()来获取垃圾数据,它可能在两个Write
调用之间,其中来自第一个的数据将被垃圾收集,但我不确定是否会发生这种情况。
答案 1 :(得分:10)
ToArray()是GetBuffer()的替代品。但是,ToArray()会在内存中创建对象的副本。如果字节大于80000,则对象将被放置在大对象堆(LOH)中。到目前为止没什么特别的。但是GC不能很好地处理LOH及其中的对象(内存未按预期释放)。因为这可能会发生OutOfMemoryException。解决方案是调用GC.Collect()以便收集这些对象或使用GetBuffer()并创建几个较小的(小于80000字节)对象 - 这些对象不会转到LOH并且内存将按预期释放由GC。
存在第三个(更好)选项,即仅使用流,例如从MemoryStream读取所有字节并直接将它们写入HttpResponse.OutputStream(再次使用字节数组< 80000字节作为缓冲区)。然而,这并不总是可行的(就像我的情况一样)。
作为总结,我们可以说当不需要对象的内存中副本时,您将不得不避免使用ToArray(),在这种情况下,GetBuffer()可能会派上用场,但可能不是最佳解决方案。
答案 2 :(得分:10)
如果您确实想要访问内部_origin值,可以使用MemoryStream.Seek(0,SeekOrigin.Begin)调用。返回值将完全是_origin值。
答案 3 :(得分:9)
.NET 4.6有一个新的API bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer)
,其精神与.GetBuffer()
类似。如果可以,此方法将返回包含_origin
信息的ArraySegment
。
有关.TryGetBuffer()
何时返回true并使用有用信息填充out参数的详细信息,请参阅this question。
答案 4 :(得分:7)
如果您使用的ArraySegment
低级API(例如Socket.Send)非常有用。而不是调用将创建数组的另一个副本的ToArray
,您可以创建一个段:
var segment=new ArraySegment<byte>(stream.GetBuffer(), 0, stream.Position);
然后将其传递给Send
方法。对于大数据,这将避免分配新阵列并将其复制到其中,这可能很昂贵。
答案 5 :(得分:4)
GetBuffer()
总是假设你知道输入字符串的数据的结构(这是它的用途)。如果您想从流中获取数据,则应始终使用提供的方法之一(例如ToArray()
)。
可以使用这样的东西,但只有我现在想到的情况才是流中的一些固定结构或虚拟文件系统。例如,在当前位置,您正在读取位于流内的文件的偏移量。然后,您可以基于此流的缓冲区创建一个新的流对象,但具有不同的_origin
。这样可以避免复制新对象的整个数据,这可以使您节省大量内存。这样可以避免携带初始缓冲区作为参考,因为您始终可以再次检索它。
答案 6 :(得分:4)
GetBuffer
MSDN documentation除了不创建数据副本外,最重要的一点是它返回一个未使用的数组 bytes:
请注意,缓冲区包含可能未使用的已分配字节。例如,如果字符串&#34; test&#34;写入MemoryStream对象,GetBuffer返回的缓冲区长度为256,而不是4,未使用252字节。要仅获取缓冲区中的数据,请使用ToArray方法;但是,ToArray会在内存中创建数据副本。
因此,如果您真的想避免因内存限制而创建副本,则必须小心不要通过网络从GetBuffer
发送整个数组或将其转储到文件或附件中,因为该缓冲区每当它被填充时,以2的幂增长,并且最后几乎总是有很多未使用的字节。
答案 7 :(得分:-1)
GetBuffer()
在需要写入二进制数据或二进制文件时非常有用。
例如,假设我正在从数据库读取数据,然后想将其中的一些数据写入二进制文件,则可以在迭代时先将数据写入缓冲区,然后一次将整个缓冲区写入文件中,从而避免了太多I / O会在每次迭代过程中禁止直接在文件中写入数据。
让我在C#
中给您一个示例:
String query = "Select Id, Name from table1";
SqlCommand cmd = new SqlCommand(query, con);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
MemoryStream memStream = new MemoryStream(10 * 1024 * 1024) // 10 Mb of membuffer
BinaryWriter memWtr = new BinaryWriter(memStream);
BinaryWriter wtrFile = new BinaryWriter(filePath);// file where you want to write your data eventually
Foreach (DataRow dr in dt.Rows)
{
memWtr.write((int)dr[0]);
memWtr.write(dr[0].ToString());
}
//now write whole buffer into File in single call
wtrFile.write(memStream.GetBuffer(), 0 , memStream.Position);
希望有帮助。