【问题标题】:How to compute hash of a large file chunk?如何计算大文件块的哈希?
【发布时间】:2013-12-17 12:58:14
【问题描述】:

我希望能够在 C# 中计算任意大小的文件块的哈希值。

例如:计算 4gb 文件中第 3g 字节的哈希值。

主要问题是我不想将整个文件加载到内存中,因为可能有多个文件,并且偏移量可能非常随意。

AFAIK,HashAlgorithm.ComputeHash 允许我使用流的字节缓冲区。流将允许我有效地计算哈希,但对于整个文件,而不仅仅是特定块。

我正在考虑创建一个备用 FileStream 对象并将其传递给 ComputeHash,在那里我将重载 FileStream 方法并只读文件中的某个块。

有没有比这更好的解决方案,最好使用内置的 C# 库? 谢谢。

【问题讨论】:

  • 你可以使用TransformBlockTransformFinalBlock
  • 但这仅适用于字节数组?我想使用流的原因是因为它不需要将整个块或文件放在内存中来计算哈希。
  • ComputeHash(stream) 分段读取,在每段调用TransformBlock,最后调用TransformFinalBlock。你不需要 1GB 数组只是因为你想散列一个 1GB 块。
  • 是的,但它会从头开始读取,直到流结束?我希望它从流中的特定位置读取,直到另一个位置或某个计数之后。我不希望它一直散列到流结束。

标签: c# file hash filestream


【解决方案1】:

你应该传入:

  • 一个字节数组,包含用于计算哈希的数据块
  • 限制访问您要从中计算哈希的块的流

第二个选项并不难,这是我一起编写的一个快速的LINQPad 程序。请注意,它缺少很多错误处理,例如检查块是否实际可用(即,您传递的流的位置和长度实际存在并且不会脱离底层的末尾流)。

不用说,如果这最终成为生产代码,我会添加很多错误处理,并编写一堆单元测试以确保正确处理所有边缘情况。

您可以像这样为您的文件构造PartialStream 实例:

const long gb = 1024 * 1024 * 1024;
using (var fileStream = new FileStream(@"d:\temp\too_long_file.bin", FileMode.Open))
using (var chunk = new PartialStream(fileStream, 2 * gb, 1 * gb))
{
    var hash = hashAlgorithm.ComputeHash(chunk);
}

这是LINQPad 测试程序:

void Main()
{
    var buffer = Enumerable.Range(0, 256).Select(i => (byte)i).ToArray();
    using (var underlying = new MemoryStream(buffer))
    using (var partialStream = new PartialStream(underlying, 64, 32))
    {
        var temp = new byte[1024]; // too much, ensure we don't read past window end
        partialStream.Read(temp, 0, temp.Length);
        temp.Dump();
        // should output 64-95 and then 0's for the rest (64-95 = 32 bytes)
    }
}

public class PartialStream : Stream
{
    private readonly Stream _UnderlyingStream;
    private readonly long _Position;
    private readonly long _Length;

    public PartialStream(Stream underlyingStream, long position, long length)
    {
        if (!underlyingStream.CanRead || !underlyingStream.CanSeek)
            throw new ArgumentException("underlyingStream");

        _UnderlyingStream = underlyingStream;
        _Position = position;
        _Length = length;
        _UnderlyingStream.Position = position;
    }

    public override bool CanRead
    {
        get
        {
            return _UnderlyingStream.CanRead;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }

    public override bool CanSeek
    {
        get
        {
            return true;
        }
    }

    public override long Length
    {
        get
        {
            return _Length;
        }
    }

    public override long Position
    {
        get
        {
            return _UnderlyingStream.Position - _Position;
        }

        set
        {
            _UnderlyingStream.Position = value + _Position;
        }
    }

    public override void Flush()
    {
        throw new NotSupportedException();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                return _UnderlyingStream.Seek(_Position + offset, SeekOrigin.Begin) - _Position;

            case SeekOrigin.End:
                return _UnderlyingStream.Seek(_Length + offset, SeekOrigin.Begin) - _Position;

            case SeekOrigin.Current:
                return _UnderlyingStream.Seek(offset, SeekOrigin.Current) - _Position;

            default:
                throw new ArgumentException("origin");
        }
    }

    public override void SetLength(long length)
    {
        throw new NotSupportedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        long left = _Length - Position;
        if (left < count)
            count = (int)left;
        return _UnderlyingStream.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException();
    }
}

【讨论】:

    【解决方案2】:

    您可以直接使用TransformBlockTransformFinalBlock。这与HashAlgorithm.ComputeHash 在内部所做的非常相似。

    类似:

    using(var hashAlgorithm = new SHA256Managed())
    using(var fileStream = new File.OpenRead(...))
    {     
        fileStream.Position = ...;
        long bytesToHash = ...;
    
        var buf = new byte[4 * 1024];
        while(bytesToHash > 0)
        {
            var bytesRead = fileStream.Read(buf, 0, (int)Math.Min(bytesToHash, buf.Length));
            hashAlgorithm.TransformBlock(buf, 0, bytesRead, null, 0);
            bytesToHash -= bytesRead;
            if(bytesRead == 0)
                throw new InvalidOperationException("Unexpected end of stream");
        }
        hashAlgorithm.TransformFinalBlock(buf, 0, 0);
        var hash = hashAlgorithm.Hash;
        return hash;
    };
    

    【讨论】:

      【解决方案3】:

      您的建议 - 为您的 FileStream 传递一个受限访问包装器 - 是最干净的解决方案。您的包装器应将所有内容都交给包装的 Stream 除了 LengthPosition 属性。

      怎么样?只需创建一个继承自Stream 的类。让构造函数取:

      • 您的来源Stream(在您的情况下为FileStream
      • 区块起始位置
      • 区块结束位置

      作为扩展 - 这是所有可用 http://msdn.microsoft.com/en-us/library/system.io.stream%28v=vs.100%29.aspx#inheritanceContinuedStreams 的列表

      【讨论】:

      • 其实你还需要在Read方法中添加一些控件,否则会读到“子流”的末尾。
      【解决方案4】:

      要轻松计算较大流的一个块的哈希,请使用以下两种方法:

      这里有一个 LINQPad 程序演示:

      void Main()
      {
          const long gb = 1024 * 1024 * 1024;
          using (var stream = new FileStream(@"d:\temp\largefile.bin", FileMode.Open))
          {
              stream.Position = 2 * gb; // 3rd gb-chunk
              byte[] buffer = new byte[32768];
              long amount = 1 * gb;
      
              using (var hashAlgorithm = SHA1.Create())
              {
                  while (amount > 0)
                  {
                      int bytesRead = stream.Read(buffer, 0,
                          (int)Math.Min(buffer.Length, amount));
      
                      if (bytesRead > 0)
                      {
                          amount -= bytesRead;
                          if (amount > 0)
                              hashAlgorithm.TransformBlock(buffer, 0, bytesRead,
                                  buffer, 0);
                          else
                              hashAlgorithm.TransformFinalBlock(buffer, 0, bytesRead);
                      }
                      else
                          throw new InvalidOperationException();
                  }
                  hashAlgorithm.Hash.Dump();
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        回答你原来的问题(“有没有更好的解决方案...”):

        我不知道。

        这似乎是一项非常特殊、不平凡的任务,因此无论如何都可能涉及一些额外的工作。我认为您使用自定义 Stream 类的方法是正确的,我可能会做同样的事情。

        Gusdorxander 已经提供了关于如何实现它的非常有用的信息——干得好!

        【讨论】:

          猜你喜欢
          • 2023-03-14
          • 2015-12-18
          • 2020-12-16
          • 2011-11-05
          • 2012-05-06
          • 2011-10-24
          • 2017-09-23
          • 2016-12-30
          • 2010-12-17
          相关资源
          最近更新 更多