遅延ストリームチャンク列挙子を実装する方法は?

私は、バイトストリームをサイズが大きくなるように分割しようとしています。

ソースストリームには未知数のバイトが含まれており、読み込みにコストがかかります。列挙子の出力は、8KBから1MBまでのサイズが増加するバイト配列でなければなりません。

これは、ストリーム全体を読み込んで配列に格納し、関連する部分を取り出すだけで簡単に行うことができます。ただし、ストリームが非常に大きい場合があるため、一度に読み込むことは不可能です。また、パフォーマンスは主な関心事ではありませんが、システムの負荷を非常に低く抑えることが重要です。

これを実装する中で、コードを短く保守しにくいのは比較的難しいことに気付きました。ストリームに関連するいくつかの問題もあります(たとえば、Stream.Readが成功してもバッファをいっぱいにしないなど)。

私は私の場合に役立つ既存のクラスは見つけられなかったし、ネット上で何かを見つけることもできなかった。どのようにそのようなクラスを実装しますか?

3
@Rup:1回だけ繰り返す。バイト[]でなければなりません。ソースストリーム全体が大きすぎて完全に読み込めませんが、個々のチャンクはメモリに収まります。すべてのチャンクのサイズと位置はわかっていますが、ソースストリームの実際の長さは不明です(最終チャンクは想定されているサイズより小さくなる可能性があります)。
追加された 著者 mafu,
あなたはそれを何度も繰り返すでしょうか?イテレータがストリームを返すか、byte [] sを返す必要がありますか?ストリームの場合は、移動する前に完全に読み込まれますか?もしそうなら、小さいバッファだけを使ってソースストリームをより小さなストリームに分割するイテレータを書くのはかなり簡単でしょう。あるいは、最初にチャンクのサイズを知っていますか?つまり、バッファを割り当てて一度にチャンク全体を読むことができますか?終わりのマーカーを見つける?
追加された 著者 Rup,

2 答え

public IEnumerable getBytes(Stream stream)
{
    List bufferSizes = new List() { 8192, 65536, 220160, 1048576 };
    int count = 0;
    int bufferSizePostion = 0;
    byte[] buffer = new byte[bufferSizes[0]];
    bool done = false;
    while (!done)
    {
        BufferWrapper nextResult = new BufferWrapper();
        nextResult.bytesRead = stream.Read(buffer, 0, buffer.Length);
        nextResult.buffer = buffer;
        done = nextResult.bytesRead == 0;
        if (!done)
        {
            yield return nextResult;
            count++;
            if (count > 10 && bufferSizePostion < bufferSizes.Count)
            {
                count = 0;
                bufferSizePostion++;
                buffer = new byte[bufferSizes[bufferSizePostion]];
            }
        }
    }
}

public class BufferWrapper
{
    public byte[] buffer { get; set; }
    public int bytesRead { get; set; }
}

明らかに、バッファサイズで上に移動するときのロジックと、そのサイズを選択する方法が変更される可能性があります。

最も効率的な方法ではないので、送信される最後のバッファを処理するためのより良い方法が見つかるかもしれません。

3
追加された
stream.Readにバグがあります:読み取り専用のEOFは0を返します(bytesRead
追加された 著者 mafu,
@Rup:読み込んだバイト数が少なくなるため、より小さなバッファが必要になります。
追加された 著者 mafu,
@Rup:右。私は余分なバイトを読んで幻覚でしたが、あなたはそれを非常に簡単に制限することができます(バッファサイズから既存のサイズを差し引いたもの)
追加された 著者 mafu,
バイト配列と実際の長さ(実データ)の両方を格納するバイト配列のための独自のラッパーを作成し、毎回それを返すことができたことが私にはたまたまでした。これは、最後の部分バッファの配列コピーを処理する必要がないことを意味します。
追加された 著者 Servy,
私はいくつかのことを修正した。私自身のコメントと、部分バッファーがEOFを意味するわけではないという事実を取り入れました。気づいてきれいにしたいくつかのバグもありました。
追加された 著者 Servy,
+1、なぜコピーバッファ?あなたがそれを読み込んだバッファを返すだけで、常に新しいバッファを割り当てるのはなぜですか?
追加された 著者 Rup,
しかし、それを修正するのは簡単です。バッファがいっぱいになるか0バイトが返されるまで、バッファの残りの部分を埋めるように読み込みを再試行してください。
追加された 著者 Rup,

参考までに、私が現在使っている実装は、@Servyの答え

private const int InitialBlockSize = 8 * 1024;
private const int MaximumBlockSize = 1024 * 1024;

private Stream _Stream;
private int _Size = InitialBlockSize;

public byte[] Current
{
    get;
    private set;
}

public bool MoveNext ()
{
    if (_Size < 0) {
        return false;
    }

    var buf = new byte[_Size];
    int count = 0;

    while (count < _Size) {
        int read = _Stream.Read (buf, count, _Size - count);

        if (read == 0) {
            break;
        }

        count += read;
    }

    if (count == _Size) {
        Current = buf;
        if (_Size <= MaximumBlockSize/2) {
            _Size *= 2;
        }
    }
    else {
        Current = new byte[count];
        Array.Copy (buf, Current, count);
        _Size = -1;
    }

    return true;
}
2
追加された