3

我正在将一些文件异步下载到一个大字节数组中,并且每当将一些数据添加到该数组时,我都会定期触发回调。如果我想让开发人员能够使用添加到数组的最后一块数据,那么……我该怎么做呢?在 C++ 中,我可以给他们一个指向中间某处的指针,然后也许告诉他们在最后一次操作中添加的字节数,这样他们至少知道他们应该查看的块......我真的不知道想给他们该数据的第二份副本,那只是浪费。

我只是在想人们是否想在文件完成下载之前处理这些数据。真的有人愿意这样做吗?还是无论如何它是一个无用的功能?当缓冲区(整个字节数组)已满时,我已经有一个回调,然后他们可以转储整个事情而不用担心起点和终点......

4

7 回答 7

11

.NET 有一个结构可以完全满足您的要求:

System.ArraySegment

无论如何,您自己也很容易实现它——只需创建一个构造函数,它接受一个基本数组、一个偏移量和一个长度。然后实现一个索引器,在后台偏移索引,这样你的 ArraySegment 就可以无缝地用于代替数组。

于 2009-12-29T01:41:27.583 回答
3

你不能给他们一个指向数组的指针,但你可以给他们数组和新数据的起始索引和长度。

但我不得不想知道有人会用这个做什么。这是已知的需求吗?或者你只是猜测有一天有人可能想要这个。如果是这样,您是否有任何理由迫不及待地想在某人真正需要它时添加该功能?

于 2009-12-29T01:13:07.530 回答
1

复制一大块字节数组可能看起来“浪费”,但话说回来,像 C# 这样的面向对象语言往往比过程语言更浪费一点。一些额外的 CPU 周期和一点额外的内存消耗可以大大降低开发过程的复杂性并增加灵活性。事实上,对我来说,将字节复制到内存中的新位置听起来像是一个不错的设计,而不是指针方法,它可以让其他类访问私有数据。

但是,如果您确实想使用指针,C# 确实支持它们。这是一个看起来不错的教程。作者说得对,“......只有在执行速度非常重要的 C# 中才真正需要指针”。

于 2009-12-29T01:14:08.370 回答
1

这是否需要取决于您是否有能力在处理文件之前从文件中累积所有数据,或者您是否需要提供一种流模式,以便在每个块到达时对其进行处理。这取决于两件事:有多少数据(您可能不想累积数 GB 的文件),以及文件完全到达需要多长时间(如果您通过慢速链接获取数据,您可能不会希望您的客户等到它全部到达)。因此,根据库的使用方式,添加它是一个合理的特性。流模式通常是一个理想的属性,所以我会投票支持实现该功能。但是,将数据放入数组的想法似乎是错误的,因为它从根本上意味着非流式设计,并且需要额外的副本。相反,您可以做的是将每一块到达的数据保留为离散的部分。这些可以存储在一个容器中,最后添加和从前面移除是有效的。

于 2009-12-29T02:24:09.273 回答
1

我同意 OP:有时您只是需要注意效率。我不认为提供 API 的例子是最好的,因为这肯定需要安全和简单而不是效率。

然而,一个简单的例子是在处理大量包含无数记录的巨大二进制文件时,例如在编写解析器时。如果不使用诸如 System.ArraySegment 之类的机制,解析器就会成为一个大内存占用者,并且会通过创建无数新数据元素、复制所有内存以及从堆中分片来大大减慢速度。这是一个非常现实的性能问题。我一直在为电信行业编写此类解析器,这些解析器每天在多个类别中的每个类别中生成数百万条记录,这些记录来自许多具有可变长度二进制结构的交换机,需要将其解析到数据库中。

使用 System.ArraySegment 机制与为每条记录创建新的结构副本相比,极大地加快了解析速度,并大大降低了解析器的峰值内存消耗。这些都是非常真实的优势,因为服务器运行多个解析器,经常运行它们,并且速度和内存节省 = 非常实际的成本节省,因为不必有这么多专用于解析的处理器。

System.Array 段非常易于使用。这是一个简单的示例,它提供了一种基本方法来跟踪典型的大二进制文件中的各个记录,该文件充满了具有固定长度标题和可变长度记录大小的记录(明显的异常控制已删除):

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;
        } 
    }
}

上面的示例为您提供了一个包含文件中每条记录的 ArraySegments 列表,同时将所有实际数据保留在每个文件的一个大数组中。唯一的开销是 MyRecord 结构中每条记录的两个数组段。处理记录时,您拥有 MyRecord.header.Array 和 MyRecord.data.Array 属性,它们允许您对每条记录中的元素进行操作,就好像它们是它们自己的 byte[] 副本一样。

于 2010-07-22T01:21:50.273 回答
0

我想你不应该打扰。

为什么会有人想要使用它?

于 2009-12-29T00:58:10.743 回答
0

这听起来像你想要一个事件

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));
于 2009-12-29T01:38:29.803 回答