我只是在想,如果有人想在文件下载完成之前处理这些数据。任何人真正需要这样做吗?或者这只是一个无用的功能?我已经为缓冲区(整个字节数组)填满时设置了一个回调函数,然后他们可以将整个缓冲区倾倒出来,而不必担心起始和结束点...
我只是在想,如果有人想在文件下载完成之前处理这些数据。任何人真正需要这样做吗?或者这只是一个无用的功能?我已经为缓冲区(整个字节数组)填满时设置了一个回调函数,然后他们可以将整个缓冲区倾倒出来,而不必担心起始和结束点...
.NET有一个结构体可以满足您的需求:
无论如何,您也可以轻松地自己实现它-只需创建一个构造函数,它接受一个基本数组、一个偏移量和一个长度。然后实现一个索引器,在幕后偏移索引,这样您的ArraySegment就可以无缝地用于替代数组。
你不能给他们一个指向数组的指针,但是你可以给他们数组和新数据的起始索引和长度。
但我不得不想知道有人会用这个做什么。这是一个已知的需求吗?还是你只是猜测有一天可能会有人需要它。如果是这样的话,有没有任何理由为什么你不能等到有人真正需要它时再添加这个功能呢?
我同意原帖的观点:有时候你确实需要关注效率。我认为提供API的例子并不是最好的,因为这当然需要在安全性和简单性方面倾向于效率。
然而,一个简单的例子是处理大量具有无数记录的巨大二进制文件,例如编写解析器时。如果不使用System.ArraySegment等机制,解析器将成为一个大内存占用者,并且通过创建无数新数据元素、复制所有内存并且使堆碎片化来大大减慢速度。这是一个非常真实的性能问题。我经常为电信设备编写这些类型的解析器,每天从多个交换机中生成数百万条记录,每个交换机都有多个类别的可变长度二进制结构需要解析到数据库中。
使用System.ArraySegment机制与为每个记录创建新结构副本相比,极大地加快了解析速度,并大大降低了解析器的峰值内存消耗。这些都是非常真实的优势,因为服务器运行多个解析器,频繁运行它们,速度和内存保护=在不必要地拥有太多专门用于解析的处理器方面节省了很多成本。
System.Array segment非常容易使用。这里有一个简单的例子,提供了一种基本的方法来跟踪典型的大型二进制文件中的每个记录,该文件包含一个固定长度的头部和可变长度的记录大小(明显的异常控制已删除)。public struct MyRecord
{
ArraySegment<byte> header;
ArraySegment<byte> data;
}
public class Parser
{
const int HEADER_SIZE = 10;
const int HDR_OFS_REC_TYPE = 0;
const int HDR_OFS_REC_LEN = 4;
byte[] m_fileData;
List<MyRecord> records = new List<MyRecord>();
bool Parse(FileStream fs)
{
int fileLen = (int)fs.FileLength;
m_fileData = new byte[fileLen];
fs.Read(m_fileData, 0, fileLen);
fs.Close();
fs.Dispose();
int offset = 0;
while (offset + HEADER_SIZE < fileLen)
{
int recType = (int)m_fileData[offset];
switch (recType) { /*puke if not a recognized type*/ }
int varDataLen = ((int)m_fileData[offset + HDR_OFS_REC_LEN]) * 256
+ (int)m_fileData[offset + HDR_OFS_REC_LEN + 1];
if (offset + varDataLen > fileLen) { /*puke as file has odd bytes at end*/}
MyRecord rec = new MyRecord();
rec.header = new ArraySegment(m_fileData, offset, HEADER_SIZE);
rec.data = new ArraySegment(m_fileData, offset + HEADER_SIZE,
varDataLen);
records.Add(rec);
offset += HEADER_SIZE + varDataLen;
}
}
}
复制字节数组的一部分可能看起来有些“浪费”,但是面向对象的语言(如C#)通常比过程式语言更加浪费。在开发过程中,多花一些CPU周期和一点额外的内存消耗可以大大降低复杂性并增加灵活性。实际上,将字节复制到内存中的新位置对我来说听起来像是很好的设计,而不是指针方法,这将使其他类可以访问私有数据。
但是,如果您确实想使用指针,C#也支持它们。这里有一个看起来不错的教程。 作者在他陈述“...只有在C#中执行速度非常重要时才真正需要指针”时是正确的。
是否需要这个功能取决于您是否能够在处理数据之前累积文件中的所有数据,或者您是否需要提供流模式,在此模式下,您可以在到达每个块时处理它。这取决于两件事:有多少数据(您可能不想累积多个千兆字节的文件),以及文件完全到达需要多长时间(如果您通过缓慢的链接获取数据,则可能不希望客户端等待直到所有数据都到达)。因此,根据库的使用方式,添加此功能是合理的。流模式通常是一个理想的属性,因此我建议实现此功能。但是,将数据放入数组的想法似乎是错误的,因为它从根本上意味着非流设计,并且需要额外的复制。相反,您可以将到达的每个数据块保留为单独的块。这些可以存储在容器中,对于在末尾添加和从前面删除是有效的。
HttpWebRequest
构建一个包装器(WebClient
无法满足我的所有需求)。据我所知,我可以在其中执行的唯一异步读取操作(BeginRead
)将数据放入byte[]
数组中(对我来说,这是一个足够好的格式)。大多数情况下,我通过查看ContentLength
标头来知道数组的最终大小,因此我可以适当地分配内存。但是,如果文件很大,我已经指定了一个上限,在达到该上限时,数据可以转储到文件中(我已经实现了“缓冲区满”回调)。 - mpen这听起来像是你想要一个event。
public class ArrayChangedEventArgs : EventArgs {
public (byte[] array, int start, int length) {
Array = array;
Start = start;
Length = length;
}
public byte[] Array { get; private set; }
public int Start { get; private set; }
public int Length { get; private set; }
}
// ...
// and in your class:
public event EventHandler<ArrayChangedEventArgs> ArrayChanged;
protected virtual void OnArrayChanged(ArrayChangedEventArgs e)
{
// using a temporary variable avoids a common potential multithreading issue
// where the multicast delegate changes midstream.
// Best practice is to grab a copy first, then test for null
EventHandler<ArrayChangedEventArgs> handler = ArrayChanged;
if (handler != null)
{
handler(this, e);
}
}
// finally, your code that downloads a chunk just needs to call OnArrayChanged()
// with the appropriate args
客户端钩入事件并在事情发生变化时被调用。这是大多数 .NET 客户端代码在 API 中期望拥有的功能(“当某些事情发生时请通知我”)。他们可以使用以下简单代码来钩入代码:
yourDownloader.ArrayChanged += (sender, e) =>
Console.WriteLine(String.Format("Just downloaded {0} byte{1} at position {2}.",
e.Length, e.Length == 1 ? "" : "s", e.Start));
我认为你不应该费心。
天哪,为什么有人想要使用它呢?