3

いくつかのファイルを大きなバイト配列に非同期でダウンロードしています。その配列にデータが追加されるたびに定期的に発生するコールバックがあります。開発者が配列に追加されたデータの最後のチャンクを使用できるようにしたい場合は、どうすればよいでしょうか? C++ では、途中のどこかへのポインターを与えることができ、最後の操作で追加されたバイト数を伝えて、少なくとも見るべきチャンクを知ることができます...私は本当に知りませんそのデータの 2 番目のコピーを提供したいのですが、それは無駄です。

ファイルのダウンロードが完了する前に、人々がこのデータを処理したいかどうかを考えています。誰かが実際にそれをしたいですか?それともとにかく役に立たない機能ですか?バッファ(バイト配列全体)がいっぱいになったときのコールバックがすでにあり、開始点と終了点を気にせずにすべてをダンプできます...

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

OPに同意します。効率に注意を払う必要がある場合があります。API を提供するという例が最善だとは思いません。なぜなら、効率よりも安全性とシンプルさに傾倒する必要があるからです。

ただし、単純な例として、パーサーを作成する場合など、膨大な数のレコードを含む大量の巨大なバイナリ ファイルを処理する場合があります。System.ArraySegment などのメカニズムを使用しないと、パーサーは大量のメモリを消費し、無数の新しいデータ要素を作成し、すべてのメモリをコピーし、ヒープから完全に断片化することで大幅に速度が低下します。これは非常に現実的なパフォーマンスの問題です。私はこの種のパーサーを通信用に常に作成しており、データベースに解析する必要がある可変長のバイナリ構造を持つ多くのスイッチのそれぞれから、いくつかのカテゴリのそれぞれで 1 日に何百万ものレコードを生成します。

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 を含むリストが得られますが、ファイルごとに 1 つの大きな配列にすべての実際のデータが配置されています。唯一のオーバーヘッドは、レコードごとの MyRecord 構造体の 2 つの配列セグメントです。レコードを処理するとき、MyRecord.header.Array および MyRecord.data.Array プロパティを使用して、各レコードの要素を独自の byte[] コピーであるかのように操作できます。

于 2010-07-22T01:21:50.273 に答える
1

これが必要かどうかは、ファイルを処理する前にファイルからすべてのデータを蓄積する余裕があるかどうか、または到着した各チャンクを処理するストリーミング モードを提供する必要があるかどうかによって異なります。これは、データの量 (数ギガバイトのファイルを蓄積したくない可能性があります) と、ファイルが完全に到着するまでにかかる時間 (低速リンクを介してデータを取得している場合) の 2 つに依存します。すべてが到着するまでクライアントを待ちたい)。そのため、ライブラリの使用方法によっては、追加するのが合理的な機能です。通常、ストリーミング モードは望ましい属性であるため、この機能の実装に投票します。ただし、データを配列に入れるという考えは、基本的に非ストリーミング設計を意味し、追加のコピーが必要になるため、間違っているようです。代わりにできることは、到着するデータの各チャンクを個別のピースとして保持することです。これらは、最後に追加して前から削除するのが効率的なコンテナに格納できます。

于 2009-12-29T02:24:09.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 に答える