Linq To Objects - 如何操作文件目录
开篇语:
上次发布的 《LINQ:进阶 - LINQ 标准查询操作概述》 社会反响不错,但自己却始终觉得缺点什么!“纸上得来终觉浅,绝知此事要躬行”,没错,就是实战!这次让我们一起来看看一些操作文件目录的技巧,也许能引我们从不同的角度思考问题,从而走出思维的死角!
这是与 《Linq To Objects - 如何操作字符串》 风格相似的操作技巧篇。
这里主要贴的是代码,所以会很枯燥乏味,要知道,放弃远远比一章章的看下去要简单得多。博主猜测:看的每一小点都可能令你有“柳暗花明又一村”之意!
序
--演示如何对出现在指定目录树的多个位置的所有文件名进行分组。此外,还演示如何根据自定义比较器执行更复杂的比较。
一、如何查询具有指定属性或名称的文件
此示例演示如何查找指定目录树中具有指定文件扩展名(例如“.txt”)的所有文件,还演示如何根据创建时间返回树中最新或最旧的文件。
1 //该查询将所有生产的完整路径。txt文件指定的文件夹包括子文件夹下。 2 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\"; 3 //取文件系统快照 4 var dir = new DirectoryInfo(path); 5 //该方法假定应用程序在指定路径下的所有文件夹都具有搜索权限。 6 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 7 8 //创建查询 9 var fileQuery = from file in files 10 where file.Extension == ".html" 11 orderby file.Name 12 select file; 13 14 //执行查询 15 foreach (var file in fileQuery) 16 { 17 Console.WriteLine(file.FullName); 18 } 19 20 //创建和执行一个新的查询,通过查询旧文件的创建时间作为一个出发点 21 //Last:选最后一个,因为是按日期升序,所以最新的是指最后一个 22 var newestFile = (from file in fileQuery 23 orderby file.CreationTime 24 select new { file.FullName, file.CreationTime }).Last(); 25 26 Console.WriteLine( 27 $"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");
图中执行结果可能与您的不一样。
1 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7"; 2 //“path”的长度,后续用于在输出时去掉“path”这段前缀 3 var trimLength = path.Length; 4 //取文件系统快照 5 var dir = new DirectoryInfo(path); 6 //该方法假定应用程序在指定路径下的所有文件夹都具有搜索权限。 7 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 8 9 //创建查询 10 var query = from file in files 11 group file by file.Extension.ToLower() into fileGroup 12 orderby fileGroup.Key 13 select fileGroup; 14 15 //一次显示一组。如果列表实体的行数大于控制台窗口中的行数,则分页输出。 16 PageOutput(trimLength, query);
1 private static void PageOutput(int rootLength, IOrderedEnumerable<IGrouping<string, FileInfo>> query) 2 { 3 //跳出分页循环的标志 4 var isAgain = true; 5 //控制台输出的高度 6 var numLines = Console.WindowHeight - 3; 7 8 //遍历分组集合 9 foreach (var g in query) 10 { 11 var currentLine = 0; 12 13 do 14 { 15 Console.Clear(); 16 Console.WriteLine(string.IsNullOrEmpty(g.Key) ? "[None]" : g.Key); 17 18 //从“currentLine”开始显示“numLines”条数 19 var resultPage = g.Skip(currentLine).Take(numLines); 20 21 //执行查询 22 foreach (var info in resultPage) 23 { 24 Console.WriteLine("\t{0}", info.FullName.Substring(rootLength)); 25 } 26 27 //记录输出行数 28 currentLine += numLines; 29 Console.WriteLine("点击“任意键”继续,按“End”键退出"); 30 31 //给用户选择是否跳出 32 var key = Console.ReadKey().Key; 33 if (key != ConsoleKey.End) continue; 34 35 isAgain = false; 36 break; 37 } while (currentLine < g.Count()); 38 39 if (!isAgain) 40 { 41 break; 42 } 43 } 44 45 }
![](https://i-blog.csdnimg.cn/blog_migrate/d5fbec72ed97aae1eac139a9eb7df0bf.png)
图中执行结果可能与您的不一样。
此程序的输出可能会很长,具体取决于本地文件系统的细节以及 path 的设置。为了使您可以查看所有结果,此示例还演示如何按页查看结果。这些方法可应用于 Windows 和 Web 应用程序。请注意,由于代码将对组中的项进行分页,因此需要嵌套的 foreach 循环。此外,还会使用某他某个逻辑来计算列表中的当前位置,以及使用户可以停止分页并退出程序。在这种特定情况下,将针对原始查询的缓存结果运行分页查询。
三、如何查询一组文件夹中的总字节数
1 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#"; 2 var dir = new DirectoryInfo(path); 3 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 4 5 var query = from file in files 6 select file.Length; 7 8 //缓存结果,以避免多次访问文件系统 9 var fileLengths = query as long[] ?? query.ToArray(); 10 //返回最大文件的大小 11 var largestLength = fileLengths.Max(); 12 //返回指定文件夹下的所有文件中的总字节数 13 var totalBytes = fileLengths.Sum(); 14 Console.WriteLine(); 15 16 Console.WriteLine("There are {0} bytes in {1} files under {2}", 17 totalBytes, files.Count(), path); 18 Console.WriteLine("The largest files is {0} bytes.", largestLength);
![](https://i-blog.csdnimg.cn/blog_migrate/5a78c0d935128035dc549f113cd436c6.png)
图中执行结果可能与您的不一样。
如果您只需要统计特定目录树中的字节数,则可以更高效地实现此目的,而无需创建 LINQ 查询,因为该查询会引发创建列表集合作为数据源的系统开销。随着查询复杂度的增加,或者当您必须对同一数据源运行多个查询时,LINQ 方法的有用性也会随之增加。
四、如何比较两个文件夹中的内容
此示例演示比较两个文件列表的三种方法:
(1)查询一个指定两个文件列表是否相同的布尔值;
(2)查询用于检索同时位于两个文件夹中的文件的交集;
(3)查询用于检索位于一个文件夹中但不在另一个文件夹中的文件的差集;
1 //创建两个带比较的文件夹 2 const string path1 = @"E:\Test1"; 3 const string path2 = @"E:\Test2"; 4 5 var dir1 = new DirectoryInfo(path1); 6 var dir2 = new DirectoryInfo(path2); 7 8 //取文件快照 9 var files1 = dir1.GetFiles("*.*", SearchOption.AllDirectories); 10 var files2 = dir2.GetFiles("*.*", SearchOption.AllDirectories); 11 12 //自定义文件比较器 13 var comparer = new FileComparer(); 14 15 //该查询确定两个文件夹包含相同的文件列表,基于自定义文件比较器。查询立即执行,因为它返回一个bool。 16 var areIdentical = files1.SequenceEqual(files2, comparer); 17 Console.WriteLine(areIdentical == true ? "the two folders are the same" : "The two folders are not the same"); 18 19 //交集:找相同的文件 20 var queryCommonFiles = files1.Intersect(files2, comparer); 21 22 var commonFiles = queryCommonFiles as FileInfo[] ?? queryCommonFiles.ToArray(); 23 if (commonFiles.Any()) 24 { 25 Console.WriteLine("The following files are in both folders:"); 26 foreach (var v in commonFiles) 27 { 28 Console.WriteLine(v.FullName); 29 } 30 } 31 else 32 { 33 Console.WriteLine("There are no common files in the two folders."); 34 } 35 36 //差集:对比两个文件夹的差异 37 var diffQuery = files1.Except(files2, comparer); 38 39 Console.WriteLine("The following files are in list1 but not list2:"); 40 foreach (var v in diffQuery) 41 { 42 Console.WriteLine(v.FullName); 43 }
1 //该实现定义了一个非常简单的两个 FileInfo 对象之间的比较。它只比较文件的名称和它们字节数的长度 2 public class FileComparer : IEqualityComparer<FileInfo> 3 { 4 public bool Equals(FileInfo x, FileInfo y) 5 { 6 return string.Equals(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase) && x.Length == y.Length; 7 } 8 9 //返回一个比较标准的哈希值。根据 IEqualityComparer 规则,如果相等,那么哈希值也必须是相等的。 10 //因为这里所定义的相等只是一个简单的值相等,而不是引用标识,所以两个或多个对象将产生相同的哈希值是可能的。 11 public int GetHashCode(FileInfo obj) 12 { 13 var s = string.Format("{0}{1}", obj.Name, obj.Length); 14 15 return s.GetHashCode(); 16 } 17 }
图中执行结果可能与您的不一样。
五、如何在目录树中查询最大的文件
此示例演示与文件大小(以字节为单位)相关的五种查询:
(1)如何检索最大文件的大小(以字节为单位);
(2)如何检索最小文件的大小(以字节为单位);
(3)如何从指定的根文件夹下的一个或多个文件夹检索 FileInfo 对象最大或最小文件;
(4)如何检索一个序列,如 10 个最大文件。
1 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#"; 2 var dir = new DirectoryInfo(path); 3 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 4 5 var query = from file in files 6 select file.Length; 7 8 //返回最大文件的大小 9 var maxSize = query.Max(); 10 Console.WriteLine("The length of the largest file under {0} is {1}", 11 path, maxSize); 12 13 //倒序排列 14 var query2 = from file in files 15 let len = file.Length 16 where len > 0 17 orderby len descending 18 select file; 19 20 var fileInfos = query2 as FileInfo[] ?? query2.ToArray(); 21 //倒序排列的第一个就是最大的文件 22 var longestFile = fileInfos.First(); 23 //倒序排列的第一个就是最小的文件 24 var smallestFile = fileInfos.Last(); 25 26 Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes", 27 path, longestFile.FullName, longestFile.Length); 28 Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes", 29 path, smallestFile.FullName, smallestFile.Length); 30 Console.WriteLine("===== The 10 largest files under {0} are: =====", path); 31 32 //返回前10个最大的文件 33 var queryTenLargest = fileInfos.Take(10); 34 foreach (var v in queryTenLargest) 35 { 36 Console.WriteLine("{0}: {1} bytes", v.FullName, v.Length); 37 }
![](https://i-blog.csdnimg.cn/blog_migrate/a741f236c52cc0ddfb85ee48b27d76e3.png)
若要返回一个或多个完整的 FileInfo 对象,查询必须首先检查数据源中的每个对象,然后按这些对象的 Length 属性的值排序它们。然后查询可以返回具有最大长度的单个对象或序列。使用 First<TSource> 可返回列表中的第一个元素。使用 Take<TSource> 可返回前 n 个元素。指定降序排序顺序可将最小的元素放在列表的开头。
六、如何在目录树中查询重复的文件
1 static void Main(string[] args) 2 { 3 QueryDuplicates(); 4 //QueryDuplicates2(); 5 6 Console.ReadKey(); 7 }
1 static void QueryDuplicates() 2 { 3 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 12.0"; 4 var dir = new DirectoryInfo(path); 5 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 6 var charsToSkip = path.Length; 7 8 var queryDupNames = (from file in files 9 group file.FullName.Substring(charsToSkip) by file.Name into fileGroup 10 where fileGroup.Count() > 1 11 select fileGroup).Distinct(); 12 13 PageOutput<string, string>(queryDupNames); 14 }
1 private static void QueryDuplicates2() 2 { 3 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 12.0"; 4 var dir = new DirectoryInfo(path); 5 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 6 //路径的长度 7 var charsToSkip = path.Length; 8 9 //注意一个复合键的使用。三个属性都匹配的文件属于同一组。 10 //匿名类型也可以用于复合键,但不能跨越方法边界。 11 var queryDupFiles = from file in files 12 group file.FullName.Substring(charsToSkip) by 13 new PortableKey() { Name = file.Name, CreationTime = file.CreationTime, Length = file.Length } 14 into fileGroup 15 where fileGroup.Count() > 1 16 select fileGroup; 17 18 var queryDupNames = queryDupFiles as IGrouping<PortableKey, string>[] ?? queryDupFiles.ToArray(); 19 var list = queryDupNames.ToList(); 20 var i = queryDupNames.Count(); 21 22 //分页输出 23 PageOutput<PortableKey, string>(queryDupNames); 24 25 }
1 private static void PageOutput<TK, TV>(IEnumerable<IGrouping<TK, TV>> queryDupNames) 2 { 3 //跳出分页循环的标志 4 var isAgain = true; 5 var numLines = Console.WindowHeight - 3; 6 7 var dupNames = queryDupNames as IGrouping<TK, TV>[] ?? queryDupNames.ToArray(); 8 foreach (var queryDupName in dupNames) 9 { 10 //分页开始 11 var currentLine = 0; 12 13 do 14 { 15 Console.Clear(); 16 Console.WriteLine("Filename = {0}", queryDupName.Key.ToString() == string.Empty ? "[none]" : queryDupName.Key.ToString()); 17 18 //跳过 currentLine 行,取 numLines 行 19 var resultPage = queryDupName.Skip(currentLine).Take(numLines); 20 21 foreach (var fileName in resultPage) 22 { 23 Console.WriteLine("\t{0}", fileName); 24 } 25 26 //增量器记录已显示的行数 27 currentLine += numLines; 28 29 //让用户自动选择下一下 30 //Console.WriteLine("Press any key to continue or the 'End' key to break..."); 31 //var key = Console.ReadKey().Key; 32 //if (key == ConsoleKey.End) 33 //{ 34 // isAgain = false; 35 // break; 36 //} 37 38 //按得有点累,还是让它自动下一页吧 39 Thread.Sleep(100); 40 41 } while (currentLine < queryDupName.Count()); 42 43 //if (!isAgain) 44 // break; 45 } 46 47 }
图中执行结果可能与您的不一样。
1 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 12.0"; 2 var dir = new DirectoryInfo(path); 3 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); 4 5 //待匹配的字符串 6 const string searchTerm = @"Visual Studio"; 7 //搜索每个文件的内容。 8 //您也可以使用正则表达式替换 Contains 方法 9 var queryMatchingFiles = from file in files 10 where file.Extension == ".html" 11 let content = GetFileConetnt(file.FullName) 12 where content.Contains(searchTerm) 13 select file.FullName; 14 15 //执行查询 16 Console.WriteLine("The term \"{0}\" was found in:", searchTerm); 17 foreach (var filename in queryMatchingFiles) 18 { 19 Console.WriteLine(filename); 20 }
1 /// <summary> 2 /// 读取文件的所有内容 3 /// </summary> 4 /// <param name="fileName"></param> 5 /// <returns></returns> 6 static string GetFileConetnt(string fileName) 7 { 8 //如果我们在快照后已删除该文件,则忽略它,并返回空字符串。 9 return File.Exists(fileName) ? File.ReadAllText(fileName) : ""; 10 }
图中执行结果可能与您的不一样。
================================================== 传送门分割线 ==================================================
LINQ 其它随笔 - 《开始使用 LINQ》
================================================== 传送门分割线 ==================================================
【首联】http://www.cnblogs.com/liqingwen/p/5816051.html
【参考】https://msdn.microsoft.com/zh-cn/library/bb397911(v=vs.100).aspx