C# 计算输入和输出 FileStream 的 MD5

Posted

技术标签:

【中文标题】C# 计算输入和输出 FileStream 的 MD5【英文标题】:C# Calculate an MD5 of an Input and Output FileStream 【发布时间】:2022-01-14 15:59:06 【问题描述】:

我正在使用 this(稍作修改)从文件共享中复制大文件,如果下载中断,可以继续复制。它在 BackroudWorker 中运行并报告进度。这工作正常,但我希望能够在每次将文件数据块写入磁盘时以最小的额外开销将当前的 MD5 哈希写入磁盘(当前总数,而不是每个块一次)。如果发现部分文件,我想从文件中读取 MD5 哈希,如果它与部分文件相同,则继续复制。当文件被完全复制后,文件中的 MD5 哈希值应该是完全复制文件的 MD5 哈希值。我想稍后用它来确定源文件和目标文件是相同的。感谢您的帮助!

这是我目前的复制方法:

        public static bool CopyFile(List<CopyObjects> FileList, FSObjToCopy job, BackgroundWorker BW)
    
        Stopwatch sw = new Stopwatch();
        long RestartPosition = 0;
        bool Retry = false;
        int BYTES_TO_READ = (0x200000)
        foreach (CopyObjects co in FileList)
        
            FileInfo fi = co.file;
            FileInfo fo = null;

            if (fi.Directory.FullName.StartsWith($@"Test_Updater_Core.ServerName\Test_Updater_Core.ServerTemplateRoot"))
            

                if (File.Exists(fi.FullName.Replace($@"Test_Updater_Core.ServerName\Test_Updater_Core.ServerTemplateRoot", $@" Test_Updater_Core.USBStore_Drive.driveInfo.Name.Replace("\\", "")\Test_Updater_Core.UsbTemplateRoot")))
                
                    fi = new FileInfo(fi.FullName.Replace($@"Test_Updater_Core.ServerName\Test_Updater_Core.ServerTemplateRoot", $@"Test_Updater_Core.USBStore_Drive.driveInfo.Name.Replace("\\", "")\Test_Updater_Core.UsbTemplateRoot"));
                    co.destination = co.destination.Replace($@"Test_Updater_Core.USBStore_Drive.driveInfo.Name.Replace("\\", "")\Test_Updater_Core.UsbTemplateRoot", $@"Test_Updater_Core.LocalInstallDrive\Test_Updater_Core.LocalTemplateRoot");
                    fo = new FileInfo($"fi.FullName.Replace($@"Test_Updater_Core.USBStore_Drive.driveInfo.Name.Replace("\\", "")\Test_Updater_Core.UsbTemplateRoot", $@"Test_Updater_Core.LocalInstallDrive\Test_Updater_Core.LocalTemplateRoot")Test_Updater_Core.TempFileExtension");
                
            
            
            //If a clean cancellation was requested, we do it here, otherwise the BackgroundWorker will be killed
            if (BW.CancellationPending)
            
                job.Status = FSObjToCopy._Status.Complete;
                return false;
            
            //If a pause is requested, we loop here until resume or termination has been signaled
            while (job.PauseBackgroundWorker == true)
            
                Thread.Sleep(100);
                if (BW.CancellationPending)
                
                    job.Status = FSObjToCopy._Status.Complete;
                    return false;
                
                Application.DoEvents();
            
            if (fo == null)
                fo = new FileInfo($"fi.FullName.Replace(job.Source, co.destination)Test_Updater_Core.TempFileExtension");

            if (fo.Exists)
            
                Retry = true;
                RestartPosition = fo.Length - BYTES_TO_READ;
            
            else
            
                RestartPosition = 0;
                Retry = false;
            
            if (RestartPosition <= 0)
            
                Retry = false;
            

            sw.Start();

            try
            
                // Read source files into file streams
                FileStream source = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read);
                // Additional way to write to file stream
                FileStream dest = new FileStream(fo.FullName, FileMode.OpenOrCreate, FileAccess.Write);
                // Actual read file length
                int destLength = 0;
                // If the length of each read is less than the length of the source file, read in chunks
                if (BYTES_TO_READ < source.Length)
                
                    byte[] buffer = new byte[BYTES_TO_READ];
                    long copied = 0;
                    if (Retry)
                    
                        source.Seek(RestartPosition, SeekOrigin.Begin);
                        dest.Seek(RestartPosition, SeekOrigin.Begin);
                        Retry = false;
                    
                    while (copied <= source.Length - BYTES_TO_READ)
                    
                        destLength = source.Read(buffer, 0, BYTES_TO_READ);

                        source.Flush();
                        dest.Write(buffer, 0, BYTES_TO_READ);
                        dest.Flush();
                        // Current position of flow
                        dest.Position = source.Position;
                        copied += BYTES_TO_READ;
                        job.CopiedSoFar += BYTES_TO_READ;
                        if (sw.ElapsedMilliseconds > 250)
                        
                            job.PercComplete = (int)(float)((float)job.CopiedSoFar / (float)job.TotalFileSize * 100);

                            sw.Restart();
                            sw.Start();
                            job.ProgressCell.Value = job.PercComplete;
                            BW.ReportProgress(job.PercComplete < 100 ? job.PercComplete : 99);
                        
                        if (BW.CancellationPending)
                        
                            job.Status = FSObjToCopy._Status.Complete;
                            return false;
                        
                        while (job.PauseBackgroundWorker == true)
                        
                            Thread.Sleep(100);
                            if (BW.CancellationPending)
                            
                                job.Status = FSObjToCopy._Status.Complete;
                                return false;
                            
                            Application.DoEvents();
                        
                    
                    int left = (int)(source.Length - copied);
                    destLength = source.Read(buffer, 0, left);
                    source.Flush();
                    dest.Write(buffer, 0, left);
                    dest.Flush();
                    job.CopiedSoFar += left;
                
                else
                
                    // If the file length of each copy is longer than that of the source file, the actual file length is copied directly.
                    byte[] buffer = new byte[source.Length];
                    source.Read(buffer, 0, buffer.Length);
                    source.Flush();
                    dest.Write(buffer, 0, buffer.Length);
                    dest.Flush();
                    job.CopiedSoFar += source.Length;
                    job.PercComplete = (int)(float)((float)job.CopiedSoFar / (float)job.TotalFileSize * 100);
                    job.ProgressCell.Value = job.PercComplete;
                    BW.ReportProgress(job.PercComplete < 100 ? job.PercComplete : 99);
                
                source.Close();
                dest.Close();
                fo.LastWriteTimeUtc = fi.LastWriteTimeUtc;
                if (File.Exists(fo.FullName))
                
                    if (File.Exists(fo.FullName.Replace($"Test_Updater_Core.TempFileExtension", "")))
                    
                        File.Delete(fo.FullName.Replace($"Test_Updater_Core.TempFileExtension", ""));
                    
                    File.Move(fo.FullName, fo.FullName.Replace($"Test_Updater_Core.TempFileExtension", ""));
                
                job.ProgressCell.Value = job.PercComplete;
                BW.ReportProgress(job.PercComplete);
            
            catch (Exception ex)
            
                MessageBox.Show($"There was an error copying:Environment.NewLinefiEnvironment.NewLineto:" +
                    $"Environment.NewLinefoEnvironment.NewLineThe error is: Environment.NewLineex.Message",
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                job.Status = FSObjToCopy._Status.Error;
                return false;
            
            finally
            
                sw.Stop();
            
        
        return true;
    

【问题讨论】:

有一行“md5InFile.TransformBlock(buffer,)”不应该存在。抱歉! BackgoundWorker 类自 2012 年以来已过时,完全被 async/await、Task.Run 和 IProgress 取代。 MD5 类有一个 ComputeHashAsync(Stream,CancellationToken) 方法,这意味着所有这些代码都可以替换为 4-5 行 您还可以同时处理多个文件,这是 BGW 无法做到的 @Panagiotis Kanavos:太棒了!愿意分享吗? 我其实每批文件都有一个BGW。所以每次调用 CopyFile(List FileList, FSObjToCopy job, BackgroundWorker BW) 实际上有多个相关文件(FileList),但是可以并行调用。 【参考方案1】:

我决定在服务器上创建包含一系列校验和的校验和文件。当我复制文件时,我将校验和添加到内部列表中,并将它们与服务器列表进行比较。如果在某些时候,它们不匹配,我会回到它们相同的位置并从那里重新开始。在复制作业结束时,我将内部列表中的校验和写入磁盘,与服务器的名称相同。如果我想检查文件的完整性,我可以将服务器文件与本地文件进行比较并验证校验和。

【讨论】:

以上是关于C# 计算输入和输出 FileStream 的 MD5的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的Stream流

C# 中的Stream流

FileStream类的使用

对FileStream的几种属性和方法认识

对FileStream的几种属性和方法认识

C# 之 FileStream类介绍