技术无极限

Nio的技术空间
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
利用Dataview实现搜索指定目录下的所有文件,以指定的条件排序(可以按文件名升降序,最后修改时间升降序)

    在我的应用中,有这样一个案例,测试机为每个已测试的PCB板或者成品生成一份测试报表,为了得知它们的测试详细情况,需要实时捕获各测试条目的结果,所以在服务器上有一Parser分析这些文件,更新到数据库.当然也可以写一个dll给测试程序去调用及更新数据库,但测试机由不同的供应商提供,很难做到这点.
    已知测试机有许多,有许多不同的测试工位,同一时间会产生大量的文件,相临工位有流程控制要求,即上一工位没有Pass记录,下一工位不允许通过.所以Parser要按时间的先后顺序来处理测试报表.查询MSDN帮助文档,并没有实现这个要求相关的类,所以利用DataView的SoryBy方法来实现它,实现的道理很简单,从源目录得到文件名及属性,构造一个DataTable,一列为文件名,另一列为最后修改时间,这样就可以用SortBy方法来处理它了,呵呵,有点取巧.

下面先来了解下C#中操作文件系统的类:
.Net中,有关文件系统操作的类都在System.IO命名空间内,有如下图的类用于浏览文件系统和执行操作.



这些类的作用是:

.System.MarshalByRefObject--.Net类中用于远程操作的基对象类,允许在应用程序域之间调用数据
.FileSystemInfo--表示任何文件系统对象的基类
.FileInfo和File--表示文件系统上的文件
.DirectoryInfo和Directory--表示文件系统上的文件夹
.Path--这个类包含的静态成员可以用于处理路径名

其中,Directory和File只包含静态方法,不能被实例化,适于单次操作,DirectoryInfo和FileInfo是有状态的,使用时需要实例化,适用于多次操作的场合.另外,Directory和File每次都要进行安全检查,而DirectoryInfo和FileInfo只有实例化的时候才检查.

类图如下:



好了,有了这些基本的知识,我们先来实现一个简单的方法:得到一个目录下的所有文件
首先,引入必须的命名空间:
using System;
using System.Data;
using System.IO;                  //文件操作类所在的命名空间
using System.Collections;   //ArrayList类所在的命名空间

 1         /// <summary>
 2         /// 返回指定目录(包括子目录)的所有的文件列表,找到的文件是系统随机的
 3         /// </summary>
 4         /// <param name="path">有效的目录</param>
 5         /// <returns>ArrayList类型</returns>
 6         public ArrayList SearchFile(string path){
 7             if (!IsDirectory(path)){
 8                 return null;
 9             }
10             DirectoryInfo dir = new DirectoryInfo(path);
11             FileSystemInfo[] entryies = dir.GetFileSystemInfos();
12             FileSystemInfo file;
13 
14             for (int i=0;i<entryies.Length;i++){
15                 file=entryies[i];
16                 //如果是一个文件
17                 if(file.GetType()==typeof(FileInfo)){
18                     fileList.Add(file.FullName);
19                 }
20                 //如果是一个目录
21                 else if(file.GetType()==typeof(DirectoryInfo)){
22                     SearchFile(file.FullName);
23                 }
24             }
25             return fileList;
26         }

上面的代码用到了一个方法IsDirectory,它只是Directory.Exists()方法的封装,用起来直观一点而已:

 1         /// <summary>
 2         /// 检查指定的路径是不是一个有效的目录
 3         /// </summary>
 4         /// <param name="dirPath">需要判断的路径名</param>
 5         /// <returns>是有效的目录,返回true</returns>
 6         public static bool IsDirectory(string dirPath) {
 7             if (!Directory.Exists(dirPath)) {
 8                 return false;
 9             }
10             return true;
11         }

下面再来增加一点点内容,要求返回以指定后缀名指定大小的文件列表:

 1         /// <summary>
 2         /// 返回指定目录(包括子目录)的指定数目的文件,未排序,系统随机生成
 3         /// </summary>
 4         /// <param name="path">有效的目录</param>
 5         /// <param name="filter">文件类型(*.*,mp3,txt等),多个格式以|号分隔</param>
 6         /// <param name="top">返回的文件个数,这个参数为负,返回所有找到的文件</param>
 7         public ArrayList SearchFile(string path,string filter,int top){
 8             if (!IsDirectory(path)){
 9                 return null;
10             }
11 
12             DirectoryInfo dir = new DirectoryInfo(path);
13             //指定目录下的文件或者文件夹
14             FileSystemInfo[] entryies = dir.GetFileSystemInfos();
15             FileSystemInfo file;
16 
17             for (int i=0;i<entryies.Length;i++){
18                 //top为负,返回所有找到的文件
19                 if (top>0){
20                     //已达指定数目的文件,则返回
21                     if (fileList.Count>=top){
22                         return fileList;
23                     }
24                 }
25                 file=entryies[i];
26                 //如果是一个文件
27                 if(file.GetType()==typeof(FileInfo)){
28                     //*.*返回所有文件
29                     if (filter=="*.*")
30                         fileList.Add(file.FullName);
31                     else{
32                         //得到文件过滤器数组
33                         string[] filters = filter.Split(new char[]{'|'});
34                         foreach(string flt in filters){
35                             if (file.Name.ToLower().EndsWith(flt.ToLower()))
36                                 fileList.Add(file.FullName);
37                         }
38                     }
39                 }
40                 //如果是一个目录
41                 else if(file.GetType()==typeof(DirectoryInfo)){
42                     //递归搜索
43                     SearchFile(file.FullName,filter,top);
44                 }
45             }
46             return fileList;
47         }

有了上面的基础,再来实现一个可排序文件名的方法,这下用到了System.Data命名空间的DataTable等,本来原先自已写了一个快速排序和一个冒泡排序,可与DataView的排序速度差了一个数量级,有人说Windows程序员永远不要认为自已做得比M$好,这话说得不错.废话少说,下面先来初始化一个DataTable,构造一个两列的DataTable,一个是文件名,另一个最后修改时间:

 1         /// <summary>
 2
         /// 初始化
 3         /// </summary>
 4         private void Init(){
 5             _dt = new DataTable("FilesTable");
 6             _dt.Columns.Add("FileName"typeof(string));
 7             _dt.Columns.Add("LastUpdated"typeof(DateTime));
 8             _ds = new DataSet("FileList");
 9             _ds.Tables.Add(_dt);
10             _dv = new DataView(_ds.Tables[0]);
11             fileList= new ArrayList();
12         }

搜索指定目录,填充DataTable,实现方法和上面的SearchFile方法差不多,只不过是用DataTable代替ArrayList而已:

 1         /// <summary>
 2         /// 得到指定目录下所有指定文件格式的文件,包括子目录
 3         /// </summary>
 4         /// <param name="path">有效的目录</param>
 5         /// <param name="filter">文件类型(*.*,mp3,txt等),*.*返回所有文件,
 6         /// 多种格式以|字符分隔,如mp3|txt|log,不区分大小写</param>
 7         private void FileScout(string path,string filter){
 8             DirectoryInfo dir = new DirectoryInfo(path);
 9             FileSystemInfo[] entryies = dir.GetFileSystemInfos();
10             FileSystemInfo file ;
11 
12             for (int i=0;i<entryies.Length;i++){
13                 file=entryies[i];
14                 //如果是一个文件
15                 if(file.GetType()==typeof(FileInfo)){
16                     _dr = _dt.NewRow();
17                     if (filter == "*.*"){
18                         _dr[0= file.FullName.ToString();
19                         _dr[1= file.LastWriteTime;
20                         //把文件名和最后修改时间加入DataTable
21                         _dt.Rows.Add(_dr);
22                     }
23                     else{
24                         //得到文件过滤器数组
25                         string[] filters = filter.Split(new char[]{'|'});
26                         foreach(string flt in filters){
27                             if(file.Name.ToLower().EndsWith(flt.ToLower())){
28                                 _dr[0= file.FullName.ToString();
29                                 _dr[1= file.LastWriteTime;
30                                 //把文件名和最后修改时间加入DataTable
31                                 _dt.Rows.Add(_dr);
32                             }
33                         }
34                     }
35                 }
36                 //如果是一个目录,递归调用FileScout方法
37                 else if(file.GetType()==typeof(DirectoryInfo)){
38                     FileScout(file.FullName,filter);
39                 }
40             }
41         }
42 

接着定义一个枚举类型,用以方便调用者:

 1         /// <summary>
 2         /// 排序类型,按指定的条件排序
 3         /// </summary>
 4         public enum SortBy{
 5             /// <summary>
 6             /// 按文件名升序
 7             /// </summary>
 8             FileNameASC,
 9             /// <summary>
10             /// 按文件名降序
11             /// </summary>
12             FileNameDESC,
13             /// <summary>
14             /// 按最后修改时间升序
15             /// </summary>
16             LastUpdatedASC,
17             /// <summary>
18             /// 按最后修改时间降序
19             /// </summary>
20             LastUpdatedDESC
21         }

有了上面的方法,接下来就是按条件排序了:

 1         /// <summary>
 2         /// 返回一个指定目录指定大小已排序的文件列表,如果top参数小于0,返回所有文件
 3         /// </summary>
 4         /// <param name="path">有效的目录名</param>
 5         /// <param name="filter">文件类型(*.*,mp3,txt等),*.*返回所有文件,
 6         /// 多种格式以|字符分隔,如mp3|txt|log,不区分大小写</param>
 7         /// <param name="sortBy">按条件排序,文件名升降序,修改时间升降序</param>
 8         /// <param name="top" >返回指定记录大小</param>
 9         public ArrayList FileSorter(string path,string filter,FileSearcher.SortBy sortBy,int top){
10             if (!IsDirectory(path)){
11                 return null;
12             }
13 
14             //搜索指定目录下的所有文件
15             FileScout(path,filter);
16             
17             if (_dt!=null){
18                 switch (sortBy){
19                     case SortBy.FileNameASC:
20                         _dv.Sort = "FileName ASC";
21                         break;
22                     case SortBy.FileNameDESC:
23                         _dv.Sort = "FileName DESC";
24                         break;
25                     case SortBy.LastUpdatedASC:
26                         _dv.Sort = "LastUpdated ASC";
27                         break;
28                     case SortBy.LastUpdatedDESC:
29                         _dv.Sort = "LastUpdated DESC";
30                         break;
31                     default:
32                         _dv.Sort = "LastUpdated ASC";
33                         break;
34                 }
35                 for(int i=0;i<_dv.Count;i++){
36                     //top为负数,返回所有的文件
37                     if(top>0){
38                         if (fileList.Count < top)
39                             fileList.Add(_dv[i]["FileName"].ToString());
40                         else
41                             return fileList;    //已找到指定数量的文件
42                     }
43                     else
44                         fileList.Add(_dv[i]["FileName"].ToString());
45                 }
46             }
47             else {
48                 return null;
49             }
50             return fileList;
51         }

DataView排序速度挺快的,在我HP4016(1.73G,60G,1024M)上搜索我的音乐目录,按最后修改时间找到20000多个音乐及歌词文件费时11036毫秒,主要是IO的时间吧,第二次搜索排序2700左右.不过内存占用就没法说了..Net打开一个简单的WinForm就10多M了,示例程序在搜索排序期间最多时占内存38M多了.我们的服务器有4G内存,反正从没看到超过2G过.

呵呵..刚学C#,水平有限,本不想放到首页来的,但周围找不到同好,更没有高手指点,所以进展有限,所以想让更多的人看到,帮我指点迷津,谢了.

完整代码和示例可以从这里下载: