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

agg*_*k02 33 .net memorystream getbuffer

我知道,GetBuffer()在C#/一个MemoryStream.NET必须小心使用,因为,作为文档描述了这里,有可能在年底未使用的字节,所以你必须要确保只在第一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;
}
Run Code Online (Sandbox Code Playgroud)

所以要对返回的缓冲区做任何事情GetBuffer(),你真的需要知道_origin.唯一的问题是_origin是私有的,没有办法实现它......

所以我的问题是-什么是使用GetBuffer()MemoryStream()无将MemoryStream是如何构造的一些先验知识(这是什么套_origin)?

(正是这个构造函数,只有这个构造函数,设置原点 - 当你想要一个字节数组的MemoryStream从字节数组中的特定索引开始时:

public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
Run Code Online (Sandbox Code Playgroud)

)

For*_*gic 16

答案是在GetBuffer()MSDN文档中,您可能错过了它.

当您创建一个MemoryStream不提供字节数组(byte[])时:

它创建了一个初始化为零的可扩展容量.

换句话说,byte[]Write在Stream上进行调用时,MemoryStream将引用具有适当大小的a .

因此,GetBuffer()您可以直接访问底层数组并读取它.

当您处于不知道其大小的情况下将收到流时,这可能很有用.如果收到的流通常非常大,那么呼叫GetBuffer()要比调用ToArray()引擎盖下的数据复制要快得多,见下文.

要仅获取缓冲区中的数据,请使用ToArray方法; 但是,ToArray会在内存中创建数据副本.

我想知道你可能在一开始就调用GetBuffer()来获取垃圾数据,它可能是两次Write调用之间,第一次调用的数据会被垃圾收集,但我不确定是否会发生这种情况.

  • @Vikhram问题是:什么时候MemoryStream上的GetBuffer()有用吗?我解释了一个非常好的例子,当它有用时. (4认同)

小智 12

如果您确实想要访问内部_origin值,可以使用MemoryStream.Seek(0,SeekOrigin.Begin)调用.返回值将完全是_origin值.

  • 这应该是公认的答案.另外,看一下源代码,如果用户已经为构造函数提供了索引,则MemoryStream _origin只有!= 0.当MemoryStream本身分配内存时,_origin始终为0. (2认同)
  • 同意。如果使用 .NET 4.5 或更早版本,这是最佳选择。如果您使用的是 4.6 或更高版本,下面的 TryGetBuffer 答案在美观上会更好一些。 (2认同)

Unk*_*own 11

ToArray()是GetBuffer()的替代品.但是,ToArray()会在内存中创建对象的副本.如果字节大于80000,则对象将被放置在大对象堆(LOH)中.到目前为止没什么特别的.但是GC不能很好地处理LOH及其中的对象(内存未按预期释放).因为这可能会发生OutOfMemoryException.解决方案是调用GC.Collect()以便收集这些对象或使用GetBuffer()并创建几个较小的(小于80000字节)对象 - 这些对象不会转到LOH并且内存将按预期释放由GC.

存在第三个(更好)选项,即仅使用流,例如从MemoryStream读取所有字节并直接将它们写入HttpResponse.OutputStream(再次使用字节数组<80000字节作为缓冲区).然而,这并不总是可行的(就像我的情况一样).

作为总结,我们可以说当不需要对象的内存中副本时,您将不得不避免使用ToArray(),在这种情况下,GetBuffer()可能会派上用场,但可能不是最佳解决方案.

  • 我很确定原始海报知道 ToArray 创建了一个副本,因此 GetBuffer 可以提高性能。但是,问题是“如何正确使用 GetBuffer”,您的回答没有解决这个问题。如果不了解 _origin 字段,我们就无法知道返回缓冲区中的真实数据从哪里开始。 (3认同)

chw*_*arr 9

.NET 4.6有一个新的API,bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer)在精神上类似于.GetBuffer().如果可以ArraySegment,此方法将返回包含_origin信息的方法.

有关何时返回true并使用有用信息填充out参数的详细信息,请参阅此问题.TryGetBuffer().


Sea*_*ean 8

如果您使用的是低级API ArraySegment,例如Socket.Send,它会很有用.ToArray您可以创建一个段,而不是调用哪个将创建该阵列的另一个副本:

var segment=new ArraySegment<byte>(stream.GetBuffer(), 0, stream.Position);
Run Code Online (Sandbox Code Playgroud)

然后将其传递给Send方法.对于大数据,这将避免分配新阵列并将其复制到其中,这可能很昂贵.


Mar*_*rio 5

GetBuffer()始终假设您知道输入字符串的数据结构(这就是它的用途)。如果您想从流中获取数据,您应该始终使用提供的方法之一(例如ToArray())。

可以使用类似的东西,但我现在能想到的唯一情况是流中的一些固定结构或虚拟文件系统。例如,在当前位置,您正在读取流内文件的偏移量。然后,您基于该流的缓冲区创建一个新的流对象,但使用不同的_origin. 这使您无需复制新对象的整个数据,这可能使您节省大量内存。这使您无需随身携带初始缓冲区作为参考,因为您始终能够再次检索它。


Sae*_*ini 5

GetBuffer MSDN 文档中最重要的一点,除了它创建数据的副本之外,是它返回一个包含未使用字节的数组:

请注意,缓冲区包含可能未使用的已分配字节。例如,如果将字符串“test”写入 MemoryStream 对象,则 GetBuffer 返回的缓冲区长度为 256,而不是 4,其中 252 个字节未使用。要仅获取缓冲区中的数据,请使用 ToArray 方法;但是,ToArray 在内存中创建数据的副本。

因此,如果您真的想避免由于内存限制而创建副本,则必须小心不要通过网络发送整个数组GetBuffer或将其转储到文件或附件中,因为该缓冲区会在任何时候以 2 的幂增长填充并且最后几乎总是有很多未使用的字节。