C# 对文件进行MD5计算

今天在做需求的时候,需要把MD5的计算集成到应用中。其实计算MD5本身并不难,C#本身提供了计算Md5值的工具:

using (FileStream fs = File.OpenRead(path))
{
    using (var crypto = MD5.Create())
    {
        var md5Hash = crypto.ComputeHash(fs);                
        return md5Hash;
    }
}

请注意,对fs的计算是MD5提供的一个重载方法。本质上Md5是对byte[]类型进行的Hash计算,但是当你尝试得到一个大文件(比如2G以上)的byte[]时,会发现无法在c#里声明这么大的数组。因此必须以流的形式传给MD5工具类进行计算

然后可以将得到的byte[]形式存储的md5信息进行格式化

public static string GetHexString(byte[] bytes)
{
    string hexString = bytes.Aggregate(string.Empty, (res, b) => res = res + b.ToString("X2"));
    return hexString;
}

这样得到的就是32位的十六进制字符串了,这里可以用Convert方法计算,不过Convert好像印象中需要自己再处理一下连字符-,我当时没顾得查API,考虑到对性能影响不大,就用这种方式写了

这是一种同步的计算方式,但是会卡IO,特别是对多个文件进行Md5计算的时候,任务进度不好掌控。实际上Md5还提供了另一个流式运算(分块计算)

public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount);

大概意思就是,你可以提供一个比较小的缓冲区inputBuffer用来给MD5工具进行迭代计算,在计算最后一个Hash块时,需要调用TransformFinalBlock方法,这会返回给你最终的Hash数组。我是按照参考调用Md5.Hash也可以获得最终的Hash结果

这样的好处就是,你可以把摘要计算做成一个异步的方法。因为文件流的读取本身就可以异步,可以分块。你只需要每帧读取一部分的数据,然后传给MD5进行部分计算,缓存结果,这样整个流程就可控了

下边是我自己封装的一个分段计算Md5的类,可以找到分段的参考。去掉了一些不必要的信息

using System;
using System.IO;
using System.Security.Cryptography;

class Md5CheckTask
{
    public bool IsComplete;
    public float Progress;
    public string Result;
    public string Path;

    private MD5 _crypto;

    private Stream _stream;

    private byte[] _cache;

    private long _curLen;

    private int _blockSize =  1024 * 1024;

    private int _readLen = 0;

    private byte[] _outPut;

    public Md5CheckTask(string path)
    {
        Path = path;
        _crypto = MD5.Create();
        _stream = File.OpenRead(path);
        _cache = new byte[_blockSize];
        _outPut = new byte[_blockSize];
        _curLen = 0;
        IsComplete = false;
    }

    private void Finish()
    {
        _crypto.TransformFinalBlock(_cache, 0, 0);
        _stream.Close();
        //这里就是最终的MD5字符串
        string s = "MD5: " + Helper.GetHexString(_crypto.Hash);     
    }

    public void Update()
    {
        if (IsComplete)
        {
            return;
        }

        _readLen = _stream.Read(_cache, 0, _blockSize);

        if (_readLen > 0)
        {
            _curLen += _readLen;
            Progress = (float)((double)_curLen / _stream.Length);
            _crypto.TransformBlock(_cache, 0, _readLen, _outPut, 0);
        }

        if (_curLen >= _stream.Length)
        {
            IsComplete = true;
            Finish();
            return;
        }
    }
}

其中Update()方法是由外部驱动的,我这里是每帧调用,并且在类里记录了Progress用来给外部做进度条展示

这样就不会卡死UI,效果要好的多

然后就是_blockSize的大小了,这里定义的是1M,太小的话,速度会很慢,太大的话,卡顿感明显,而且会掉帧,导致又像是同步的了,所以这里选一个合适的就好。因为我的任务是从共享盘里计算文件的大小,所以还有一个局域网带宽的限制。这种参数,还是根据实际调整一下为好。

你可能感兴趣的:(技术栈,c#,md5)