我是靠谱客的博主 儒雅钢笔,最近开发中收集的这篇文章主要介绍本地文件同步——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:testa"/>
<!--目标路径-->
<add key="obj" value="e:testb"/>
<!--日记文件路径-->
<add key="logs" value="e:testlogs.txt"/>
<!--自动同步时间,单位为毫秒-->
<add key="synTime" value="5000"/>
</appSettings>
</configuration>

 


效果:

 

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

最后

以上就是儒雅钢笔为你收集整理的本地文件同步——C#源代码的全部内容,希望文章能够帮你解决本地文件同步——C#源代码所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(34)

评论列表共有 0 条评论

立即
投稿
返回
顶部