本地文件同步——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>

 


效果:

 

希望本篇文章对你有所用处。

posted on 2012-07-19 00:54  Ron Ngai  阅读(10644)  评论(27编辑  收藏  举报

导航