C# 文件去重

最近用迅雷下载了很多文件,很多都是点默认内容直接下载的。屯了很多垃圾广告,一想到有这么多重复文件在里面,就感觉不舒服。

于是乎,直接整了个去重的程序:

C# 文件去重_第1张图片

程序逻辑是,先比较文件大小,如果文件大小相同,再比较md5和Hash值,如果都相同,就认为是重复文件。当然md5和Hash都是弱相关,有极小概率出错(大概16的72次方分之一),但为了我宝贵的资源,我还是不直接删除重复文件,而是把文件放到指定目录下,让我人工删除。

软件里做了日志,方便查验。

实测1.61TB视频,用时10分钟。16000张图片,用时30秒。

第一个文本框是路径,会遍历路径下的所有子文件夹。第二个框是文件类型,输入空则不限定类型,可限定多种类型。

整个软件工程:

链接:/s/1HiaSbglK48ri_QWwTNFfWA?pwd=cnmd 
提取码:cnmd

完整程序:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 文件去重
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            textBox1.Text = AppDomain.CurrentDomain.BaseDirectory;
        }
        class Features
        {
            public long size { get; set; }
            public string md5 { get; set; }
            public string Hash { get; set; }
            public string path { get; set; }
        }
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                button1.Enabled = false;
                button1.Text = "进行中";
                string FilePath = textBox1.Text;
                string Format = textBox2.Text;
                log("开始:" + FilePath + " 格式:" + Format);
                string RepeatFilePath = AppDomain.CurrentDomain.BaseDirectory + @"RepeatFilePath\" + DateTime.Now.ToString("HH-mm-ss");
                if (!Directory.Exists(RepeatFilePath))
                {
                    Directory.CreateDirectory(RepeatFilePath);
                }

                FileGet.getFile(FilePath, Format);
                List features = new List();

                //进度条
                progressBar1.Value = 0;
                progressBar1.Maximum = FileGet.lst.Count;
                label1.Text = progressBar1.Value + "/" + FileGet.lst.Count;
                log("共扫描到" + FileGet.lst.Count + "个文件");
                int RepeatNum = 0;
                int NoRepeatNum = 0;
                int ErrorNum = 0;

                Task.Factory.StartNew(() => {
                    foreach (FileInfo file in FileGet.lst)
                    {
                        try
                        {
                            string path = file.DirectoryName + "\\" + file.Name;
                            var index = features.FindAll(o => o.size == file.Length);//先比较文件大小
                            if (index.Count == 0)
                            {
                                Features features1 = new Features();
                                features1.size = file.Length;
                                features1.path = path;
                                features.Add(features1);
                                NoRepeatNum++;
                            }
                            else//如果文件大小相同,再计算哈希
                            {
                                //当前文件的md5和hash
                                string md5 = GetMD5HashFromFile(path);
                                string Hash = GetHash(path);

                                //log(path + " md5:" + md5);
                                //log(path + " Hash:" + Hash);

                                bool BeRepeat = false;
                                foreach (var RepeatPossible in index)
                                {
                                    if (RepeatPossible.md5 == null)
                                    {
                                        RepeatPossible.md5 = GetMD5HashFromFile(RepeatPossible.path);
                                    }
                                    if (RepeatPossible.Hash == null)
                                    {
                                        RepeatPossible.Hash = GetHash(RepeatPossible.path);
                                    }
                                    if (md5 == RepeatPossible.md5 && Hash == RepeatPossible.Hash)
                                    {
                                        //确定为同一文件
                                        string oldpath = file.DirectoryName + "\\" + file.Name;
                                        log(oldpath + " 与该文件相同: " + RepeatPossible.path);

                                        int i = 0;//重复文件重命名编号

                                        //当前文件夹下文件列表
                                        DirectoryInfo fdir = new DirectoryInfo(RepeatFilePath);
                                        FileInfo[] CurrentFile = fdir.GetFiles();
                                        List names = new List();
                                        foreach (FileInfo currentfile in CurrentFile)
                                        {
                                            names.Add(currentfile.Name);
                                        }

                                        if (names.FindIndex(o => o == file.Name) != -1)
                                        {
                                            //已有重名文件
                                            i = 0;
                                            while (names.FindIndex(o => o == "(" + i + ")" + file.Name) != -1)
                                            {
                                                i++;
                                            }
                                            file.MoveTo(RepeatFilePath + "\\(" + i + ")" + file.Name);
                                            log(oldpath + " 已移动到: " + file.DirectoryName + "\\" + file.Name);
                                        }
                                        else
                                        {
                                            file.MoveTo(RepeatFilePath + "\\" + file.Name);
                                            log(oldpath + " 已移动到: " + file.DirectoryName + "\\" + file.Name);
                                        }
                                        RepeatNum++;
                                        BeRepeat = true;
                                        break;
                                    }
                                }
                                if(!BeRepeat)
                                {
                                    NoRepeatNum++;
                                }
                            }
                            progressBar1.Value++;
                            label1.Text = progressBar1.Value + "/" + FileGet.lst.Count + " 重复文件:" + RepeatNum + " 不重复文件:" + NoRepeatNum + " 错误文件:" + ErrorNum;
                        }
                        catch (Exception ex)
                        {
                            progressBar1.Value++;
                            ErrorNum++;
                            log("错误:" + ex);
                        }
                    }
                    log("结束");
                    button1.Text = "开始";
                    DeleteNullFile(AppDomain.CurrentDomain.BaseDirectory + @"RepeatFilePath\");
                });
            }
            catch (Exception ex)
            {
                log("button1错误:" + ex.Message);
                button1.Text = "开始";
                MessageBox.Show(ex.Message, "错误");
            }
            button1.Enabled = true;
        }

        public static bool isValidFileContent(string filePath1, string filePath2)
        {
            //创建一个哈希算法对象
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(filePath1, FileMode.Open), file2 = new FileStream(filePath2, FileMode.Open))
                {
                    byte[] hashByte1 = hash.ComputeHash(file1);//哈希算法根据文本得到哈希码的字节数组
                    byte[] hashByte2 = hash.ComputeHash(file2);
                    string str1 = BitConverter.ToString(hashByte1);//将字节数组装换为字符串
                    string str2 = BitConverter.ToString(hashByte2);
                    return (str1 == str2);//比较哈希码
                }
            }
        }
        public string GetHash(string filePath)
        {
            //创建一个哈希算法对象
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file = new FileStream(filePath, FileMode.Open))
                {
                    byte[] hashByte = hash.ComputeHash(file);//哈希算法根据文本得到哈希码的字节数组
                    string str = BitConverter.ToString(hashByte);//将字节数组装换为字符串
                    return str;//返回哈希码
                }
            }
        }

        /// 
        /// 获取md5值
        /// 
        /// 文件路径
        /// 
        public string GetMD5HashFromFile(string fileName)
        {
            try
            {
                FileStream file = new FileStream(fileName, System.IO.FileMode.Open);
                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] retVal = md5.ComputeHash(file);
                file.Close();
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < retVal.Length; i++)
                {
                    sb.Append(retVal[i].ToString("x2"));
                }
                return sb.ToString();
            }
            catch (Exception ex)
            {
                throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message);
            }
        }
        /// 
        /// 日志
        /// 
        /// 日志内容
        /// 
        public static void log(string content)
        {
            string path = AppDomain.CurrentDomain.BaseDirectory + "log";
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            path = path + "\\" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
            if (!File.Exists(path))
            {
                FileStream fs = File.Create(path);
                fs.Close();
            }
            if (File.Exists(path))
            {
                StreamWriter sw = new StreamWriter(path, true, System.Text.Encoding.Default);
                sw.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff ") + content);
                sw.Close();
            }
        }
        public partial class FileGet
        {
            public static List lst = new List();
            /// 
            /// 获得目录下所有文件或指定文件类型文件(包含所有子文件夹)
            /// 
            /// 文件夹路径
            /// 扩展名可以多个 例如 .mp3.wma.rm
            /// List
            public static void getFile(string path, string extName)
            {
                lst.Clear();
                getdir(path, extName);
            }
            /// 
            /// 递归获取指定类型文件,包含子文件夹
            /// 
            /// 文件夹路径
            /// 扩展名可以多个 例如 .mp3.wma.rm
            private static void getdir(string path, string extName)
            {
                try
                {
                    string[] dir = Directory.GetDirectories(path); //文件夹列表
                    DirectoryInfo fdir = new DirectoryInfo(path);
                    FileInfo[] file = fdir.GetFiles();
                    //FileInfo[] file = Directory.GetFiles(path); //文件列表
                    if (file.Length != 0 || dir.Length != 0) //当前目录文件或文件夹不为空
                    {
                        foreach (FileInfo f in file) //显示当前目录所有文件
                        {
                            if (string.IsNullOrEmpty(extName))
                            {
                                lst.Add(f);
                            }
                            else if (extName.ToLower().IndexOf(f.Extension.ToLower()) >= 0)
                            {
                                lst.Add(f);
                            }
                        }
                        foreach (string d in dir)
                        {
                            getdir(d, extName);//递归
                        }
                    }
                }
                catch (Exception ex)
                {
                    log("getdir错误:" + ex.Message);
                    throw ex;
                }
            }
        }
        /// 
        /// 删除空文件夹
        /// 
        /// 
        private static void DeleteNullFile(string path)
        {
            try
            {
                string[] dirs = Directory.GetDirectories(path); //文件夹列表
                //为了先删除子目录再删除父目录,数组先排序再倒序,让子目录排在父目录前面
                Array.Sort(dirs);
                Array.Reverse(dirs);
                foreach (var dir in dirs)
                {
                    var info = new DirectoryInfo(dir);

                    //检查是否包含子文件夹及文件
                    if (info.GetFileSystemInfos().Length == 0)
                    {
                        //由于子文件夹或文件随时会增加,不强制删除子文件及子文件夹
                        info.Delete();
                    }
                }
            }
            catch (Exception ex)
            {
                log("DeleteNullFile错误:" + ex.Message);
                MessageBox.Show(ex.Message, "错误");
                throw ex;
            }
        }
        public partial class FileGet1
        {
            /// 
            /// 获得目录下所有文件或指定文件类型文件(包含所有子文件夹)
            /// 
            /// 文件夹路径
            /// 扩展名可以多个 例如 .mp3.wma.rm
            /// List
            public static List getFile(string path, string extName)
            {
                try
                {
                    List lst = new List();
                    string[] dir = Directory.GetDirectories(path); //文件夹列表
                    DirectoryInfo fdir = new DirectoryInfo(path);
                    FileInfo[] file = fdir.GetFiles();
                    //FileInfo[] file = Directory.GetFiles(path); //文件列表
                    if (file.Length != 0 || dir.Length != 0) //当前目录文件或文件夹不为空
                    {
                        foreach (FileInfo f in file) //显示当前目录所有文件
                        {
                            if (extName.ToLower().IndexOf(f.Extension.ToLower()) >= 0)
                            {
                                lst.Add(f);
                            }
                        }
                        foreach (string d in dir)
                        {
                            getFile(d, extName);//递归
                        }
                    }
                    return lst;
                }
                catch (Exception ex)
                {
                    log(ex.Message);
                    throw ex;
                }
            }
        }
    }
}

在编辑程序时,遇到过两个问题,第一个是移动文件时,要处理重名文件,一开始我直接用个while循环,File.Exists判断是否有重名文件,但似乎文件列表更新是另外一个线程,更新不同步。只会一直循环报错(错误文件数就是为了检测这个问题设立)。最后自己另外写个list,先把文件列表保存下来,再从列表下把文件去重,

第二个问题是文件去重比较,一开始我是同时比较大小、md5、Hash,但这样速度非常慢,因为所有文件都会读取到内存里计算md5、Hash。所以先比较大小,如果大小相同,再计算md5和Hash(因为大小相同很可能是相同文件,这时候同时计算md5和Hash),果然,去重速度大大提高了。

你可能感兴趣的:(c#,开发语言)