zoukankan      html  css  js  c++  java
  • 本地文件同步——C#源代码

    入职之后接到的第一个代码任务是一个小测试。做一个文件单向同步软件。


    需求描述:

    将文件夹A内的文件夹和文件同步到文件夹B。

    其实需求也就那么一句话,没啥还需要解释的了吧。详细点说,需要同步文件/文件夹的“新增,删除,重命名,修改”。

    一开始我的想法是先Google,然后在博客园找到这篇文章《C#文件同步工具教程》。这篇文章的核心来自msdn里面FileSystemWatcher 的解释。就是用对象FileSystemWatcher 去监听文件是否被创建,重命名,删除,修改。如果发生了就调用相对应的事件,将被修改,创建,重命名的文件复制到目标目录B当中。这个例子比较简单,很多事情都没考虑到。而且我认为用FileSystemWatcher 去监听所有的文件,太浪费CPU和内存。

     


    我的想法

    是采用递归,遍历整个源目录,对比目标目录。

      1. 如果目标目录下没有相对应的文件,将文件复制到目标目录;
      2. 如果文件在两个路径下都存在,但是文件大小和最后写入时间不一致时,将原目录下的文件复制到目标目录下;
      3. 如果文件存在于目标目录下而不存在源目录下,则将目标路径下的文件删除。

    实现 

    知道如何比较之后就可以进行递归遍历文件夹了。这个是这个软件实现的难点之一,其实也没多难,也就是说这个软件根本就没多难。以下是递归函数:

     1 /// <summary>
     2 /// 递归核心 同步目录 
     3 /// </summary>
     4 /// <param name="src">原路径</param>
     5 /// <param name="obj">目标路径</param>
     6 static void loop(string src, string obj)    // 递归核心 同步目录 
     7 {
     8     CopyFistly(src, obj);   //先同步文件
     9 
    10     //遍历文件夹,递归调用
    11     DirectoryInfo dirSrc = new DirectoryInfo(src);
    12     DirectoryInfo[] dirs = dirSrc.GetDirectories();
    13     foreach (DirectoryInfo dir in dirs)
    14     {
    15         string str = dir.Name;
    16         if (Directory.Exists(obj + "\\" + dir.Name) == false)
    17         {
    18             str = Directory.CreateDirectory(obj + "\\" + dir.Name).ToString();
    19         }
    20         //注意这里,这里是递归,而且是下面要注意的地方
    21         loop(src + "\\" + dir.ToString(), obj + "\\" + str);    
    22     }
    23 }

    测试了一下结果,在9000+个文件,40+个文件夹下,在我这部破机器上面单纯递归遍历(不复制文件)的时候需要的时间是截枝的十倍以上。简直是只乌龟。。。


    优化

    所以要想办法缩短时间提高效率。既然复制文件上面我们无法操作,那我们只好在递归上面进行优化。上个星期我发了一篇文章叫做《算法——回溯法》。这个时候刚好可以用上这种方法了。因为本身用的就是递归,而且文件夹的结构本身就是一个树的结构,在恰好满足了回溯法的要求。在遍历上面,并不需要在所有的文件夹都遍历一遍。因为有些文件夹并没有发生改变,所有就没有必要遍历下去了。所以就需要在递归调用自己之前先加一个条件,也就是加上约束函数。修改之后,代码如下:

     1 /// <summary>
     2 /// 递归核心 同步目录 
     3 /// </summary>
     4 /// <param name="src">原路径</param>
     5 /// <param name="obj">目标路径</param>
     6 static void loop(string src, string obj)    // 递归核心 同步目录 
     7 {
     8     CopyFistly(src, obj);   //先同步文件
     9 
    10     //遍历文件夹,递归调用
    11     DirectoryInfo dirSrc = new DirectoryInfo(src);
    12     DirectoryInfo[] dirs = dirSrc.GetDirectories();
    13     foreach (DirectoryInfo dir in dirs)
    14     {
    15         string str = dir.Name;
    16         if (Directory.Exists(obj + "\\" + dir.Name) == false)
    17         {
    18             str = Directory.CreateDirectory(obj + "\\" + dir.Name).ToString();
    19         }
    20         DirectoryInfo dirObj = new DirectoryInfo(str);
    21         //约束函数 在大小不一致的时候进行同步,其他状态不同步
    22         if (GetDirectoryLength(src + "\\" + dir.ToString()) != GetDirectoryLength(obj + "\\" + str))    
    23             loop(src + "\\" + dir.ToString(), obj + "\\" + str);
    24     }
    25 }

    函数GetDirectoryLength(string path)的作用是检查文件夹path的大小。这里只是简单地对比两个文件夹的大小,如果大小一致,则截枝不递归,否则递归。这种方式的效率非常高,因为很多时候并不是都在有文件的复制,所以不需要经常去遍历目录。所以截枝就好了。下面给出GetDirectoryLength(string path)函数的代码。其实该函数也是一个递归,虽然会增加负荷,但是文件多,文件夹深的时候,是很有必要的。

    获取文件夹大小
     1 /// <summary>
     2 /// 获取路径下文件夹的大小
     3 /// </summary>
     4 /// <param name="dirPath">目标路径</param>
     5 /// <returns>文件夹大小</returns>
     6 public static long GetDirectoryLength(string dirPath)
     7 {
     8     //判断给定的路径是否存在,如果不存在则退出
     9     if (!Directory.Exists(dirPath))
    10         return 0;
    11     long len = 0;
    12 
    13     //定义一个DirectoryInfo对象
    14     DirectoryInfo di = new DirectoryInfo(dirPath);
    15 
    16     //通过GetFiles方法,获取di目录中的所有文件的大小
    17     foreach (FileInfo fi in di.GetFiles())
    18     {
    19         len += fi.Length;
    20     }
    21 
    22     //获取di中所有的文件夹,并存到一个新的对象数组中,以进行递归
    23     DirectoryInfo[] dis = di.GetDirectories();
    24     if (dis.Length > 0)
    25     {
    26         for (int i = 0; i < dis.Length; i++)
    27         {
    28             len += GetDirectoryLength(dis[i].FullName);
    29         }
    30     }
    31     return len;
    32 }

    难点主要在递归和截枝的思想上面。其他方面的解释可以直接查看代码。注释已经很清楚了。下面是整个文件的源代码:

    文件同步
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.IO;
      6 using System.Threading;
      7 using System.Configuration;
      8 
      9 namespace FileSynLoop
     10 {
     11     class Program
     12     {
     13         static private string strSource = GetAppConfig("src");   //原路径
     14         static private string strObjective = GetAppConfig("obj");    //目标路径
     15         static private int synTime = 0;  //同步时间
     16         static private string flag = ""; //多线程控制标志 同步控制
     17         static private Thread threadShow;   //显示效果线程
     18         static private bool bound;  //是否使用截枝函数
     19 
     20         static void Main(string[] args)
     21         {
     22             //基本设置
     23             Console.WriteLine("原路径:" + strSource);
     24             Console.WriteLine("目标路径:" + strObjective);
     25             try { synTime = Convert.ToInt32(GetAppConfig("synTime")); }
     26             catch (Exception e) { Console.WriteLine("配置的同步时间格式不正确:" + e.Message); return; }
     27             Console.WriteLine("同步间隔时间:" + synTime + "毫秒");
     28 
     29             if (Directory.Exists(strSource) == false){Console.WriteLine("配置的原路径不存在");return;}
     30             if (Directory.Exists(strObjective) == false){Console.WriteLine("配置的目标路径不存在"); return;}
     31 
     32             Console.WriteLine("是否使用截枝函数?使用截止函数无法同步空文件夹/空文件。默认使用截枝!y/n");
     33             if (Console.ReadLine() == "n")
     34                 bound = false;
     35             else
     36                 bound = true;
     37 
     38             do{Console.WriteLine("输入ok开始!");}
     39             while (Console.ReadLine() != "ok");
     40 
     41             //线程
     42             Thread thread = new Thread(new ThreadStart(ThreadProc));
     43             threadShow = new Thread(new ThreadStart(ThreadShow));
     44             thread.Start();
     45             threadShow.Start(); //开始线程
     46             threadShow.Suspend();   //挂起线程
     47             //退出
     48             while ((flag = Console.ReadLine()) != "exit") ;
     49         }
     50 
     51         //线程控制
     52         public static void ThreadProc()
     53         {
     54             int i = 0;
     55             DateTime dt;
     56             TimeSpan ts;
     57 
     58             while (flag != "exit")
     59             {
     60                 dt = DateTime.Now;
     61                 Console.WriteLine();
     62                 Console.Write("" + ++i + "次同步开始:");
     63                 threadShow.Resume();    //恢复线程
     64                 try
     65                 {
     66                     loop(strSource, strObjective);
     67                 }
     68                 catch (Exception e)
     69                 {
     70                     Console.WriteLine("文件夹“" + strSource + "“被占用,暂时无法同步!8:" + e.Message);
     71                 }
     72                 threadShow.Suspend();   //挂起线程
     73 
     74                 ts = DateTime.Now - dt;
     75                 Console.WriteLine("|");
     76                 if (GetDirectoryLength(strSource) == GetDirectoryLength(strObjective))
     77                     Console.WriteLine("所有同步完毕!");
     78                 Console.WriteLine("" + i + "次同步结束,耗时"+ts.ToString()+",正在等待下次开始!");
     79                 Thread.Sleep(synTime);//同步时间
     80             }
     81         }
     82 
     83         //显示效果的线程
     84         public static void ThreadShow()
     85         {
     86             while (flag != "exit")
     87             {
     88                 Console.Write(">");
     89                 Thread.Sleep(500);
     90             }
     91         }
     92 
     93         /// <summary>
     94         /// 递归核心 同步目录 
     95         /// </summary>
     96         /// <param name="src">原路径</param>
     97         /// <param name="obj">目标路径</param>
     98         static void loop(string src, string obj)    // 递归核心 同步目录 
     99         {
    100             CopyFistly(src, obj);   //先同步文件
    101 
    102             //遍历文件夹,递归调用
    103             DirectoryInfo dirSrc = new DirectoryInfo(src);
    104             DirectoryInfo[] dirs = dirSrc.GetDirectories();
    105             foreach (DirectoryInfo dir in dirs)
    106             {
    107                 string str = dir.Name;
    108                 if (Directory.Exists(obj + "\\" + dir.Name) == false)
    109                 {
    110                     str = Directory.CreateDirectory(obj + "\\" + dir.Name).ToString();
    111                 }
    112                 DirectoryInfo dirObj = new DirectoryInfo(str);
    113                 if (bound)
    114                 {
    115                     //约束函数 在大小不一致的时候进行同步,其他状态不同步
    116                     if (GetDirectoryLength(src + "\\" + dir.ToString()) != GetDirectoryLength(obj + "\\" + str))    
    117                         loop(src + "\\" + dir.ToString(), obj + "\\" + str);
    118                 }
    119                 else
    120                 {
    121                     loop(src + "\\" + dir.ToString(), obj + "\\" + str);
    122                 }
    123             }
    124         }
    125 
    126         /// <summary>
    127         /// 同步文件
    128         /// </summary>
    129         /// <param name="strSource">源目录</param>
    130         /// <param name="strObjective">目标目录</param>
    131         static private void CopyFistly(string strSource, string strObjective)   //同步文件
    132         {
    133             string[] srcFileNames = Directory.GetFiles(strSource).Select(s => System.IO.Path.GetFileName(s)).ToArray(); //原路径下的所有文件
    134             string[] objFileNames = Directory.GetFiles(strObjective).Select(s => System.IO.Path.GetFileName(s)).ToArray();  //目标路径下的所有文件
    135 
    136             #region 同步新建 修改
    137             foreach (string strSrc in srcFileNames) //遍历源文件夹
    138             {
    139                 FileInfo aFile = new FileInfo(strSource + "\\" + strSrc);
    140                 string aAccessTime = aFile.LastWriteTime.ToString();
    141                 string aCreateTime = aFile.CreationTime.ToString();
    142 
    143 
    144                 string bCreateTime = "";    //目标路径文件的信息
    145                 string bAccessTime = "";
    146 
    147                 bool flag = false;
    148                 foreach (string strObj in objFileNames) //遍历目标文件夹
    149                 {
    150                     FileInfo bFile = new FileInfo(strObjective + "\\" + strObj);
    151                     bAccessTime = bFile.LastWriteTime.ToString();
    152                     bCreateTime = bFile.CreationTime.ToString();
    153 
    154                     if (strSrc == strObj)   //文件存在目标路径当中
    155                     {
    156                         if (aCreateTime != bCreateTime || aAccessTime != bAccessTime)   //文件存在但是不一致
    157                         {
    158                             try
    159                             {
    160                                 File.Copy(strSource + "\\" + strSrc, strObjective + "\\" + strSrc, true);
    161                                 FileInfo file = new FileInfo(strObjective + "\\" + strSrc);
    162                                 file.CreationTime = Convert.ToDateTime(aCreateTime);
    163                                 file.LastAccessTime = Convert.ToDateTime(aAccessTime);
    164                             }
    165                             catch (Exception e)
    166                             {
    167                                 Console.WriteLine("文件“" + strSrc + "“被占用,暂时无法同步!4:" + e.Message);
    168                             }
    169                         }
    170                         flag = true;
    171                         break;
    172                     }
    173                 }
    174 
    175                 if (flag == false)  //文件不存在目标路径当中
    176                 {
    177                     try
    178                     {
    179                         File.Copy(strSource + "\\" + strSrc, strObjective + "\\" + strSrc, true);
    180                         FileInfo file = new FileInfo(strObjective + "\\" + strSrc);
    181                         file.CreationTime = Convert.ToDateTime(aCreateTime);
    182                         file.LastAccessTime = Convert.ToDateTime(aAccessTime);
    183                     }
    184                     catch (Exception e)
    185                     {
    186                         Console.WriteLine("文件“" + strSrc + "“被占用,暂时无法同步!5" + e.Message);
    187                     }
    188                 }
    189             }
    190             #endregion
    191 
    192             #region 同步删除 重命名
    193             //删除文件
    194             foreach (string strObj in objFileNames) //遍历目标文件夹
    195             {
    196                 string allObj = strObjective + "\\" + strObj;
    197                 bool flag = false;
    198                 foreach (string strSrc in srcFileNames) //遍历源文件夹
    199                 {
    200                     if (strObj == strSrc)
    201                         flag = true;
    202                 }
    203                 if (flag == false)
    204                 {
    205                     try
    206                     {
    207                         File.Delete(allObj);
    208                     }
    209                     catch (Exception e)
    210                     {
    211                         Console.WriteLine("文件“" + strObj + "“被占用,暂时无法同步!6" + e.Message);
    212                     }
    213                 }
    214             }
    215 
    216             //删除文件夹
    217             DirectoryInfo dirSrc = new DirectoryInfo(strSource);
    218             DirectoryInfo[] dirsSrc = dirSrc.GetDirectories();
    219             DirectoryInfo dirObj = new DirectoryInfo(strObjective);
    220             DirectoryInfo[] dirsObj = dirObj.GetDirectories();
    221             foreach (DirectoryInfo bdirObj in dirsObj)
    222             {
    223                 bool flag = false;
    224                 foreach (DirectoryInfo adirSrc in dirsSrc)
    225                 {
    226                     if (bdirObj.Name == adirSrc.Name)
    227                     {
    228                         flag = true;
    229                     }
    230                 }
    231                 if (flag == false)  //如果文件夹只出现在目的路径下而不再源目录下,删除该文件夹
    232                 {
    233                     try
    234                     {
    235                         Directory.Delete(dirObj + "\\" + bdirObj, true);
    236                     }
    237                     catch (Exception e)
    238                     {
    239                         Console.WriteLine("文件夹“" + bdirObj + "“被占用,暂时无法同步!8" + e.Message);
    240                     }
    241                 }
    242             }
    243             #endregion
    244 
    245         }
    246 
    247         /// <summary>
    248         /// 获取自定义配置的值
    249         /// </summary>
    250         /// <param name="strKey">键值</param>
    251         /// <returns>键值对应的值</returns>
    252         private static string GetAppConfig(string strKey)
    253         {
    254             foreach (string key in ConfigurationManager.AppSettings)
    255             {
    256                 if (key == strKey)
    257                 {
    258                     return ConfigurationManager.AppSettings[strKey];
    259                 }
    260             }
    261             return null;
    262         }
    263 
    264         /// <summary>
    265         /// 获取路径下文件夹的大小
    266         /// </summary>
    267         /// <param name="dirPath">目标路径</param>
    268         /// <returns>文件夹大小</returns>
    269         public static long GetDirectoryLength(string dirPath)
    270         {
    271             //判断给定的路径是否存在,如果不存在则退出
    272             if (!Directory.Exists(dirPath))
    273                 return 0;
    274             long len = 0;
    275 
    276             //定义一个DirectoryInfo对象
    277             DirectoryInfo di = new DirectoryInfo(dirPath);
    278 
    279             //通过GetFiles方法,获取di目录中的所有文件的大小
    280             foreach (FileInfo fi in di.GetFiles())
    281             {
    282                 len += fi.Length;
    283             }
    284 
    285             //获取di中所有的文件夹,并存到一个新的对象数组中,以进行递归
    286             DirectoryInfo[] dis = di.GetDirectories();
    287             if (dis.Length > 0)
    288             {
    289                 for (int i = 0; i < dis.Length; i++)
    290                 {
    291                     len += GetDirectoryLength(dis[i].FullName);
    292                 }
    293             }
    294             return len;
    295         }
    296     }
    297 }

    配置文件的代码

    因为要求用配置文件,配置原路径,目的路径等信息,有必要说明一下这个问题。另外需要读配置文件,所以需要引用System.Configuration;命名空间。一下是配置文件app.config代码:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <appSettings>
        <!--原路径-->
        <add key="src" value="e:\test\a"/>
        <!--目标路径-->
        <add key="obj" value="e:\test\b"/>
        <!--日记文件路径-->
        <add key="logs" value="e:\test\logs.txt"/>
        <!--自动同步时间,单位为毫秒-->
        <add key="synTime" value="5000"/>
      </appSettings>
    </configuration>

    效果:

    希望本篇文章对你有所用处。
  • 相关阅读:
    LaTeX公式编辑器
    早期和东京,京都大学高考试题
    猎犬追狐狸试题
    矩阵方程的计算求解(Matlab实现)
    高考压轴题
    何天成:从高联到IMO金牌,超详细数学竞赛学习方法
    几个精彩的数论问题
    高考试题网站
    泛函分析有什么好的教材?
    ifndef系列
  • 原文地址:https://www.cnblogs.com/rond/p/2598489.html
Copyright © 2011-2022 走看看