【问题标题】:C# Create a FileStream with an offsetC# 创建一个带偏移量的 FileStream
【发布时间】:2015-06-26 17:09:24
【问题描述】:

问题

有没有办法在 C# 中创建一个带有偏移量的FileStream?例如,如果我在偏移 100 处打开 SomeFile.binStream.Position 将等于 0,但读取和写入将偏移 100。

背景

我正在为我的公司开发一种混合文件格式,它将机器可读的二进制数据与现代 PC 可读的 OPC 文件(本质上是使用 System.IO.Packaging 创建的 ZIP 文件)相结合。出于性能原因,二进制数据必须出现在文件的顶部。我知道 ZIP 文件将允许这样做(例如自解压档案),但不幸的是内部 ZipIOCentralDirectoryBlock 类是 hard-coded to reject ZIP files where the first file header doesn't appear at offset 0。为了避免临时文件(因为文件可以大到 3.99GB),我想欺骗ZipPackage 认为它正在处理文件的开头,而实际上它是在偏移量处读取和写入。

【问题讨论】:

  • 创建一个包装类来做到这一点并不难。
  • 创建包装器的最简单方法是什么?我应该从 FileStream 继承,还是从 Stream 继承并拥有一个 FileStream 实例?
  • 从 FileStream 继承是很自然的,但由于构造函数的数量很愚蠢,我会避免它。

标签: c# file-io zip


【解决方案1】:

当然。这是装饰器模式的完美案例:

基本上你创建一个类

  • 继承自Stream(你正在装饰的抽象类)
  • 具有接受该基类的单个实例的构造函数

然后您覆盖所有方法和属性,将调用传递给装饰实例。如果方法或属性知道流的位置或长度,您可以根据需要应用适当的调整。

编辑说明:看起来您需要如下所示装饰抽象流(没有实际打开文件的情况下无法创建文件流实例)。

这是装饰器本身的 [截断] 示例:

class OffsetStreamDecorator : Stream
{
  private readonly Stream instance ;
  private readonly long       offset   ;

  public static Stream Decorate( Stream instance )
  {
    if ( instance == null ) throw new ArgumentNullException("instance") ;

    FileStream decorator= new OffsetStreamDecorator( instance ) ;
    return decorator;
  }

  private OffsetStreamDecorator( FileStream instance )
  {
    this.instance = instance ;
    this.offset   = instance.Position ;
  }

  #region override methods and properties pertaining to the file position/length to transform the file positon using the instance's offset

  public override long Length
  {
    get { return instance.Length - offset ; }
  }

  public override void SetLength( long value )
  {
    instance.SetLength( value + offset );
  }

  public override long Position
  {
    get { return instance.Position - this.offset         ; }
    set {        instance.Position = value + this.offset ; }
  }

  // etc.

  #endregion

  #region override all other methods and properties as simple pass-through calls to the decorated instance.

  public override IAsyncResult BeginRead( byte[] array , int offset , int numBytes , AsyncCallback userCallback , object stateObject )
  {
    return instance.BeginRead( array , offset , numBytes , userCallback , stateObject );
  }

  public override IAsyncResult BeginWrite( byte[] array , int offset , int numBytes , AsyncCallback userCallback , object stateObject )
  {
    return instance.BeginWrite( array , offset , numBytes , userCallback , stateObject );
  }

  // etc.

  #endregion

}

用法非常简单,大致如下:

using ( Stream baseStream = new FileStream( @"c:\foo\bar\somefile.dat" , FileMode.Open , FileAccess.Read , FileShare.Read ) )
{

  // establish your offset
  baseStream.Seek( 100 , SeekOrigin.Begin ) ;

  using ( Stream decoratedStream = OffsetStreamDecorator.Decorate( baseStream ) )
  {
    // now you have a stream that reports a false position and length
    // which you should be able to use anywhere a Stream is accepted.

    PassDecoratedStreamToSomethingExpectingAVanillaStream( decoratedStream ) ;

  }

}

简单! (除了所有涉及的样板)

【讨论】:

  • 也许您错过了关于它是 ZIP 文件的信息。如果涉及到压缩,你可以猜出偏移量……
  • @leppie:zip 文件是一种存档 格式,包含一个或多个单独压缩的文件。这里涉及的唯一偏移量是 WRT 压缩文件本身。 zip 文件目录位于 zip 文件的末尾。目录中的每个条目都会告诉您该压缩条目在 zip 文件中的位置和长度。你寻找它,阅读八位字节并解压缩它们。我们在这里所做的只是为流建立一个不同的“零点”。
  • 对不起,我误解了这个问题。但是我的大脑编译器告诉我,您的代码中有很多编译器错误:)(暂时赞成,但请修复您的类型错误)
  • 谢谢,这看起来会做我想要的。不过,快速的问题是,如果我要像您的示例那样在内部 using 块中使用它,我是否仍然应该覆盖 Close() 和 Dispose() ?似乎这会导致 FileStream 被关闭和处置两次,或者更糟的是在处置后被意外使用。
  • 没关系,this answered my question。我不会覆盖 Close() 和 Dispose()。
猜你喜欢
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 2016-01-21
  • 1970-01-01
相关资源
最近更新 更多