什么时候MemoryStream上的GetBuffer()有用吗?

时间:2012-10-24 16:42:52

标签: .net memorystream getbuffer

我已经知道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)

8 个答案:

答案 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);

希望有帮助。