《.NET 下最快比较两个文件内容是否相同》之我的看法验证

news/2024/7/7 20:46:49

我对文件对比这一块还是比较感兴趣的,也想知道哪种方式性价比最高,效率最好,所以,根据这篇文章,我自己也自测一下,顺便留出自己对比的结果,供大佬们参考一二。

大致对比方案

我这边根据文章里的主要三个方案

  1. MD5
  2. 缓存长度读取比较
  3. 缓存长度读取(Span)比较
  4. Hash256
  5. CRC(最后补的)

新增了Hash256模式,原因是因为GitHub就是用Hash256来确定文件的唯一性的,所以,也想测试下它的性能到底如何。

又新增了CRC模式,后来才想起来的。

代码大致如下

挺简单的一个抽象工具类,方便增加相关的相同测试。
我也搜了OpenAi的建议和NewBing的建议,大致都是一样的建议。

根据建议和参考文章

抽象工具

public abstract class AFileCompare
{
    public AFileCompare(string name)
    {
        this.Name = name;
    }
    public string Name { get; set; }
    public bool Compare(string file1, string file2)
    {
        var result = false;
        Stopwatch stopwatch = Stopwatch.StartNew();
        if (Check(file1, file2))
        {
            result = CompareCore(file1, file2);
        }
        stopwatch.Stop();
        TimeSpan = stopwatch.Elapsed;
        return result;
    }
    public abstract bool CompareCore(string file1, string file2);
    public TimeSpan TimeSpan { get; set; }
    public bool Check(string file1, string file2)
    {
        if (file1 == file2)
        {
            return true;
        }
        if (new FileInfo(file1).Length == new FileInfo(file2).Length)
        {
            return true;
        }
        return false;
    }
    public override string ToString()
    {
        return $"{Name}耗时:{TimeSpan.TotalSeconds}秒";
    }
}

MD5工具

public class MD5Compare : AFileCompare
{
    public MD5Compare() : base("MD5 ")
    {
    }

    public override bool CompareCore(string file1, string file2)
    {
        using (var md5 = MD5.Create())
        {
            byte[] one, two;
            using (var fs1 = File.Open(file1, FileMode.Open))
            {
                // 以FileStream读取文件内容,计算HASH值
                one = md5.ComputeHash(fs1);
            }
            using (var fs2 = File.Open(file2, FileMode.Open))
            {
                // 以FileStream读取文件内容,计算HASH值
                two = md5.ComputeHash(fs2);
            }
            for (int i = 0; i < one.Length; i++)
            {
                if (one[i] != two[i])
                {
                    return false;
                }
            }
            return true;
        }
    }
}

Hash256

public class HashCompare : AFileCompare
{
    public HashCompare() : base("Hash256")
    {
    }

    public override bool CompareCore(string file1, string file2)
    {
        byte[] one, two;
        using (SHA1 mySHA1 = SHA1.Create())
        {
            using (FileStream stream = File.OpenRead(file1))
            {
                one = mySHA1.ComputeHash(stream);
            }
        }
        using (SHA1 mySHA1 = SHA1.Create())
        {
            using (FileStream stream = File.OpenRead(file2))
            {
                two = mySHA1.ComputeHash(stream);
            }
        }
        for (int i = 0; i < one.Length; i++)
        {
            if (one[i] != two[i])
            {
                return false;
            }
        }
        return true;
     }
}

缓存长度读取比较

public class FileSizeCompare : AFileCompare
{
    public FileSizeCompare() : base("FileSize_4096")
    {
    }

    public override bool CompareCore(string file1, string file2)
    {
        using (FileStream fs1 = new FileStream(file1, FileMode.Open))
        using (FileStream fs2 = new FileStream(file2, FileMode.Open))
        {
            byte[] buffer1 = new byte[4096];
            byte[] buffer2 = new byte[4096];

            int bytesRead1;
            int bytesRead2;

            do
            {
                bytesRead1 = fs1.Read(buffer1, 0, buffer1.Length);
                bytesRead2 = fs2.Read(buffer2, 0, buffer2.Length);

                if (bytesRead1 != bytesRead2)
                {
                    return false;
                }

                for (int i = 0; i < bytesRead1; i++)
                {
                    if (buffer1[i] != buffer2[i])
                    {
                        return false;
                    }
                }
            } while (bytesRead1 > 0);

            return true;
        }
    }
}

缓存长度读取(Span)比较

public class FileSizeCompare2 : AFileCompare
{
    public FileSizeCompare2() : base("FileSize_4096_Span")
    {
    }

    public override bool CompareCore(string file1, string file2)
    {
        using (FileStream fs1 = new FileStream(file1, FileMode.Open))
        using (FileStream fs2 = new FileStream(file2, FileMode.Open))
        {
            byte[] buffer1 = new byte[4096];
            byte[] buffer2 = new byte[4096];

            int bytesRead1;
            int bytesRead2;

            do
            {
                bytesRead1 = fs1.Read(buffer1, 0, buffer1.Length);
                bytesRead2 = fs2.Read(buffer2, 0, buffer2.Length);

                if (bytesRead1 != bytesRead2)
                {
                    return false;
                }

                if (!((ReadOnlySpan<byte>)buffer1).SequenceEqual((ReadOnlySpan<byte>)buffer2))
                {
                    return false;
                }
            } while (bytesRead1 > 0);

            return true;
        }
    }
}

CRC(补充)

public class CRCCompare : AFileCompare
{
    public CRCCompare() : base("CRC")
    {
    }
    public override bool CompareCore(string file1, string file2)
    {
        Crc32Algorithm crc32 = new Crc32Algorithm();
        byte[] one, two;
        using (var fs1 = File.Open(file1, FileMode.Open))
        {
            one = crc32.ComputeHash(fs1);
        }
        using (var fs2 = File.Open(file2, FileMode.Open))
        {
            two = crc32.ComputeHash(fs2);
        }
        for (int i = 0; i < one.Length; i++)
        {
            if (one[i] != two[i])
            {
                return false;
            }
        }
        return true;
    }
}

main 入口

static void Main(string[] args)
{
    var files = new List<(string name, string source, string target)>()
    {
        ("ubuntu_4.58 GB",@"E:\重装系统\ubuntu-22.04.2-desktop-amd64.iso",@"E:\重装系统\ubuntu-22.04.2-desktop-amd64 - 副本.iso"),
        ("Docker_523 MB",@"E:\重装系统\Docker Desktop Installer(1).exe",@"E:\重装系统\Docker Desktop Installer(1) - 副本.exe"),
        ("Postman_116 MB",@"E:\重装系统\Postman-win64-8.5.0-Setup.exe",@"E:\重装系统\Postman-win64-8.5.0-Setup - 副本.exe")
    };
    var list = new List<AFileCompare>();
    list.Add(new MD5Compare());
    list.Add(new HashCompare());
    list.Add(new FileSizeCompare());
    list.Add(new FileSizeCompare2());
    foreach ((string name, string source, string target) in files)
    {
        foreach (var item in list)
        {
            var result = item.Compare(source, target);
            Console.WriteLine($"{name} - {item.Name} 结果:{result} {item}");
        }
        Console.WriteLine();
    }
    Console.WriteLine("测试完毕");
    Console.ReadLine();
}

测试结果如下

主要是对3个文件,4.5G,500M,100M文件进行了测试,大概也能看出来点结果了。

测试1

测试2

测试3

CRC补充三次

第一次

第二次

第三次

结果对比

根据我分析的表大致可以看出来,五种方式,根据各种不同的文件对比方式,在文件大小不一样的情况下,差别还是挺大的,特别是在大文件的情况下。

文件越大,反而MD5和Hash效果会更好。
文件小于1G ?,FileSize_Span方式会更优,除了MD5,其他方式基本都差不多,CRC属于不上也不下。

虽然,环境的因素会有一定的影响,假设我这个就是客户的机器,那可能性也是存在的。

所以,在同一台机器上的多次测试也有存在的合理性。

那么,如果对性能很在意的话,可以根据文件大小与各种对比方法之间的关系,找到一个函数映射关系,从而找到一个最优的对比方法出来。

总结

还挺意外的,本来想验证这个Span方式为何快,却看到了另外问题点,出乎意料。

另外,也看到了Github采用Hash256在获取指纹上,速度还是相对稳定的。那么,用它来对比文件还是提取指纹应该是最佳之选(性能要求苛刻的,得自己搞模型求最佳函数映射关系)

还有其他的细节优化还没有验证,但是Span的方式是值得借鉴和学习的。

代码地址

https://github.com/kesshei/FileCompare.git

https://gitee.com/kesshei/FileCompare.git

一键三连呦!,感谢大佬的支持,您的支持就是我的动力!


http://lihuaxi.xjx100.cn/news/1215990.html

相关文章

[chatGPT攻略] 如何检测文本内容是否由ChatGPT生成 ?

[chatGPT攻略] 如何检测文本内容是否由ChatGPT生成 ? 在 ChatGPT 爆火的两个月内&#xff0c;学生就已经自发用这种工具做作业、写论文偷懒&#xff0c;编剧会用它编故事试试出乎人意料的故事走向&#xff0c;文案编辑用它来给自己打工。 在用工具给自己省事这件事上&#xf…

VMware虚拟机Ubuntu磁盘空间扩充详细教程

文章目录 一、写在前面二、具体步骤三、最后总结 一、写在前面 最近在做Linux内核相关实验的时候&#xff0c;发现有时候我们编译出来的内核太大&#xff0c;如果VMware虚拟机空间分配不足会导致编译Linux内核失败&#xff0c;经过摸索&#xff0c;发现可以扩充Ubuntu的磁盘空间…

【链表的分类】

链表是一种常用的数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含一个数据元素和指向下一个节点的指针。根据节点的连接方式和节点的性质&#xff0c;链表可以分为多种类型。 单向链表&#xff08;Singly Linked List&#xff09; 单向链表是最基本的链表类…

【Java】深入理解Java虚拟机 | 垃圾收集器GC

《深入理解Java虚拟机》的阅读笔记——第三章 垃圾收集器与内存分配策略。 参考了JavaGuide网站的相关内容&#xff1a;https://javaguide.cn/ Q&#xff1a;哪些内存需要回收&#xff1f;什么时候回收&#xff1f;如何回收&#xff1f; 2 对象已死吗&#xff1f; 2.1 引用…

java设计模式(十二)代理模式

目录 定义模式结构角色职责代码实现静态代理动态代理jdk动态代理cglib代理 适用场景优缺点 定义 代理模式给某一个对象提供一个代理对象&#xff0c;并由代理对象控制对原对象的引用。说简单点&#xff0c;代理模式就是设置一个中间代理来控制访问原目标对象&#xff0c;以达到…

javascript基础二十九:JavaScript如何判断一个元素是否在可视区域中?

一、用途 可视区域即我们浏览网页的设备肉眼可见的区域&#xff0c;如下图 在日常开发中&#xff0c;我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值&#xff08;例如 100 px&#xff09;&#xff0c;从而实现一些常用的功能&#xff0c;例如&#xff1a;…

每日一练 | 华为认证真题练习Day52

1、OSPFv3邻接关系无法建立&#xff0c;可能是由以下哪些原因引起&#xff1f;&#xff08;多选&#xff09; A. Router-ID冲突 B. HELLO报文发送周期不一致 C. 区域号码不一致 D. 接口IPv6地址前缀不一致 2、IPv6报文头比IPv4报文头增加了哪个字段&#xff1f; A. Versio…

READ-自动驾驶大场景神经渲染

这是一个针对自动驾驶场景的神经渲染方案&#xff0c;提出了一种大规模神经渲染方法来合成自动驾驶场景&#xff08;READ&#xff09;&#xff0c;这使得通过各种采样方案在PC上合成大规模驾驶场景成为可能。 疑问&#xff1a;文中提到基于nerf的方法和神经渲染方法&#xff0…