流包装器使Stream可以搜索?

我有一个不可搜索的只读System.IO.Stream实现(并且其Position始终返回0)。 我需要将它发送给在流上执行一些Seek操作(也就是设置Position)的消费者。 这不是一个巨大的寻求 – 比如当前位置的+/- 100。 是否存在现有的Stream包装器,它将为流添加缓冲function以进行简单的Seek操作?

更新:我应该补充一点,我的消费者是NAudio Mp3FileReader。 我真的只需要一种播放(缓慢且无限期)流式MP3的方法。 我认为这是一个错误,NAudio希望能够随意寻找他们的数据源。

寻求前锋很容易(只是阅读),但你不能在没有缓冲的情况下向后寻求。 也许只是:

 using(var ms = new MemoryStream()) { otherStream.CopyTo(ms); ms.Position = 0; // now work with ms } 

然而,这仅适用于已知结束的小到中等流(不是GB)(哪些流不需要这样做)。 如果您需要更大的流,则可以使用到临时文件的FileStream ,但会显着增加IO密集度。

这是一个包装器,可以使任何Stream可以读取操作。

它的工作原理是从底层流缓存读取,最多为构造函数中指定的字节数。 当内存限制禁止Marc Gravell的解决方案时,这将派上用场。

支持的搜寻操作:

  • 使用SeekOrigin.CurrentSeekOrigin.Begin寻求前进适用于任意偏移
  • 使用SeekOrigin.CurrentSeekOrigin.Begin向后SeekOrigin.Current从底层流中的当前位置下降到-seekBackBufferSize字节(在前一次向后搜索之后可能与readSeekableStream.Position不同)
  • 寻求使用SeekOrigin.End适用于offset >= -seekBackBufferSize && offset <= 0

一般评论

  • Seek方法和Position属性在内部完全处理,不涉及底层流(无论如何都会抛出)
  • seek只影响流的读取部分,因此是类的名称
  • 所有写操作都被简单地委托给底层流
  • 用这种方式包装已经可寻找的流将是浪费资源
  • 下面的ReadSeekableStream解决的一些问题也可以通过我的PeekableStream类来解决

这种实施是新鲜的,尚未进行战斗。 然而,我已经对相当多的搜索/读取案例和角落案例进行了unit testing,并将其与(可自由搜索的) MemoryStream交叉比较。

 public class ReadSeekableStream : Stream { private long _underlyingPosition; private readonly byte[] _seekBackBuffer; private int _seekBackBufferCount; private int _seekBackBufferIndex; private readonly Stream _underlyingStream; public ReadSeekableStream(Stream underlyingStream, int seekBackBufferSize) { if (!underlyingStream.CanRead) throw new Exception("Provided stream " + underlyingStream + " is not readable"); _underlyingStream = underlyingStream; _seekBackBuffer = new byte[seekBackBufferSize]; } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return true; } } public override int Read(byte[] buffer, int offset, int count) { int copiedFromBackBufferCount = 0; if (_seekBackBufferIndex < _seekBackBufferCount) { copiedFromBackBufferCount = Math.Min(count, _seekBackBufferCount - _seekBackBufferIndex); Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferIndex, buffer, offset, copiedFromBackBufferCount); offset += copiedFromBackBufferCount; count -= copiedFromBackBufferCount; _seekBackBufferIndex += copiedFromBackBufferCount; } int bytesReadFromUnderlying = 0; if (count > 0) { bytesReadFromUnderlying = _underlyingStream.Read(buffer, offset, count); if (bytesReadFromUnderlying > 0) { _underlyingPosition += bytesReadFromUnderlying; var copyToBufferCount = Math.Min(bytesReadFromUnderlying, _seekBackBuffer.Length); var copyToBufferOffset = Math.Min(_seekBackBufferCount, _seekBackBuffer.Length - copyToBufferCount); var bufferBytesToMove = Math.Min(_seekBackBufferCount - 1, copyToBufferOffset); if (bufferBytesToMove > 0) Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferCount - bufferBytesToMove, _seekBackBuffer, 0, bufferBytesToMove); Buffer.BlockCopy(buffer, offset, _seekBackBuffer, copyToBufferOffset, copyToBufferCount); _seekBackBufferCount = Math.Min(_seekBackBuffer.Length, _seekBackBufferCount + copyToBufferCount); _seekBackBufferIndex = _seekBackBufferCount; } } return copiedFromBackBufferCount + bytesReadFromUnderlying; } public override long Seek(long offset, SeekOrigin origin) { if (origin == SeekOrigin.End) return SeekFromEnd((int) Math.Max(0, -offset)); var relativeOffset = origin == SeekOrigin.Current ? offset : offset - Position; if (relativeOffset == 0) return Position; else if (relativeOffset > 0) return SeekForward(relativeOffset); else return SeekBackwards(-relativeOffset); } private long SeekForward(long origOffset) { long offset = origOffset; var seekBackBufferLength = _seekBackBuffer.Length; int backwardSoughtBytes = _seekBackBufferCount - _seekBackBufferIndex; int seekForwardInBackBuffer = (int) Math.Min(offset, backwardSoughtBytes); offset -= seekForwardInBackBuffer; _seekBackBufferIndex += seekForwardInBackBuffer; if (offset > 0) { // first completely fill seekBackBuffer to remove special cases from while loop below if (_seekBackBufferCount < seekBackBufferLength) { var maxRead = seekBackBufferLength - _seekBackBufferCount; if (offset < maxRead) maxRead = (int) offset; var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); _underlyingPosition += bytesRead; _seekBackBufferCount += bytesRead; _seekBackBufferIndex = _seekBackBufferCount; if (bytesRead < maxRead) { if (_seekBackBufferCount < offset) throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); return Position; } offset -= bytesRead; } // now alternate between filling tempBuffer and seekBackBuffer bool fillTempBuffer = true; var tempBuffer = new byte[seekBackBufferLength]; while (offset > 0) { var maxRead = offset < seekBackBufferLength ? (int) offset : seekBackBufferLength; var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, maxRead); _underlyingPosition += bytesRead; var bytesReadDiff = maxRead - bytesRead; offset -= bytesRead; if (bytesReadDiff > 0 /* reached end-of-stream */ || offset == 0) { if (fillTempBuffer) { if (bytesRead > 0) { Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); } } else { if (bytesRead > 0) Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); } if (offset > 0) throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes"); } fillTempBuffer = !fillTempBuffer; } } return Position; } private long SeekBackwards(long offset) { var intOffset = (int)offset; if (offset > int.MaxValue || intOffset > _seekBackBufferIndex) throw new NotSupportedException("Cannot currently seek backwards more than " + _seekBackBufferIndex + " bytes"); _seekBackBufferIndex -= intOffset; return Position; } private long SeekFromEnd(long offset) { var intOffset = (int) offset; var seekBackBufferLength = _seekBackBuffer.Length; if (offset > int.MaxValue || intOffset > seekBackBufferLength) throw new NotSupportedException("Cannot seek backwards from end more than " + seekBackBufferLength + " bytes"); // first completely fill seekBackBuffer to remove special cases from while loop below if (_seekBackBufferCount < seekBackBufferLength) { var maxRead = seekBackBufferLength - _seekBackBufferCount; var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead); _underlyingPosition += bytesRead; _seekBackBufferCount += bytesRead; _seekBackBufferIndex = Math.Max(0, _seekBackBufferCount - intOffset); if (bytesRead < maxRead) { if (_seekBackBufferCount < intOffset) throw new NotSupportedException("Could not seek backwards from end " + intOffset + " bytes"); return Position; } } else { _seekBackBufferIndex = _seekBackBufferCount; } // now alternate between filling tempBuffer and seekBackBuffer bool fillTempBuffer = true; var tempBuffer = new byte[seekBackBufferLength]; while (true) { var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, seekBackBufferLength); _underlyingPosition += bytesRead; var bytesReadDiff = seekBackBufferLength - bytesRead; if (bytesReadDiff > 0) // reached end-of-stream { if (fillTempBuffer) { if (bytesRead > 0) { Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); } } else { if (bytesRead > 0) Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead); Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff); } _seekBackBufferIndex -= intOffset; return Position; } fillTempBuffer = !fillTempBuffer; } } public override long Position { get { return _underlyingPosition - (_seekBackBufferCount - _seekBackBufferIndex); } set { Seek(value, SeekOrigin.Begin); } } protected override void Dispose(bool disposing) { if (disposing) _underlyingStream.Close(); base.Dispose(disposing); } public override bool CanTimeout { get { return _underlyingStream.CanTimeout; } } public override bool CanWrite { get { return _underlyingStream.CanWrite; } } public override long Length { get { return _underlyingStream.Length; } } public override void SetLength(long value) { _underlyingStream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { _underlyingStream.Write(buffer, offset, count); } public override void Flush() { _underlyingStream.Flush(); } } 

另一种解决方案可能是创建自己的流类,它包装另一个流。 实施Seek作为NOP。

 class MyStream : Stream { public MyStream(Stream baseStream) { this.baseStream = baseStream; } private Stream baseStream; // Delegate all operations except Seek/CanSeek to baseStream public override bool CanSeek { get { return true; } } public override long Seek(long offset, SeekOrigin origin) { return baseStream.Position; } } 

如果玩家没有充分的理由,这可能会奏效。