-
C# LINQ学习笔记四:LINQ to OBJECT之操作文件目录
本笔记摘抄自:https://www.cnblogs.com/liqingwen/p/5816051.html,记录一下学习过程以备后续查用。
许多文件系统操作实质上是查询,因此非常适合使用LINQ方法。
一、查询具有指定属性或名称的文件
此示例演示如何查找指定目录树中具有指定文件扩展名(例如“.txt”)的所有文件,还演示如何根据创建时间返回树中最新或最旧的文件。
class Program { static void Main(string[] args) { #region LINQ 查询具有指定属性或名称的文件 //文件路径 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; //取文件系统快照 var dir = new DirectoryInfo(path); //该方法假定应用程序在指定路径下的所有文件夹都具有搜索权限 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); //创建查询 var qurey = from file in files where file.Extension == ".txt" orderby file.Name select file; //执行查询 foreach (var file in qurey) { Console.WriteLine(file.FullName); } //创建和执行一个新的查询,通过查询旧文件的创建时间作为一个出发点。 //Last:选择最后一个,因为是按日期升序,所以最新的是指向最后一个。 var newestFile = (from file in qurey orderby file.CreationTime select new { file.FullName, file.CreationTime }).Last(); Console.WriteLine($"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}"); Console.Read(); #endregion } }
运行结果如下:
二、按照扩展名对文件进行分组
此示例演示如何使用LINQ对文件或文件夹列表执行高级分组和排序操作。此外,它还演示如何使用Skip<TSource>和Take<TSource>方法对控制台窗
口中的输出进行分页。
下面的查询演示如何按文件扩展名对指定目录树的内容进行分组。
class Program { static void Main(string[] args) { #region LINQ 按照扩展名对文件进行分组 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\"; //“path”的长度,后续用于在输出时去掉“path”这段前缀。 var trimLength = path.Length; //取文件系统快照 var dir = new DirectoryInfo(path); //该方法假定应用程序在指定路径下的所有文件夹都具有搜索权限。 var files = dir.GetFiles("*.*", SearchOption.AllDirectories); //创建查询 var query = from file in files group file by file.Extension.ToLower() into fileGroup orderby fileGroup.Key select fileGroup; //一次显示一组。如果列表实体的行数大于控制台窗口中的行数,则分页输出。 PageOutput(trimLength, query); #endregion } /// <summary> /// 分页输出 /// </summary> /// <param name="rootLength"></param> /// <param name="query"></param> private static void PageOutput(int rootLength, IOrderedEnumerable<IGrouping<string, FileInfo>> query) { //跳出分页循环的标志 var isAgain = true; //控制台输出的高度 var numLines = Console.WindowHeight - 3; //遍历分组集合 foreach (var g in query) { var currentLine = 0; do { Console.Clear(); Console.WriteLine(string.IsNullOrEmpty(g.Key) ? "[None]" : g.Key); //从“currentLine”开始显示“numLines”条数 var resultPage = g.Skip(currentLine).Take(numLines); //执行查询 foreach (var info in resultPage) { Console.WriteLine("\t{0}", info.FullName.Substring(rootLength)); } //记录输出行数 currentLine += numLines; Console.WriteLine("点击“任意键”继续,按“End”键退出"); //给用户选择是否跳出 var key = Console.ReadKey().Key; if (key != ConsoleKey.End) continue; isAgain = false; break; } while (currentLine < g.Count()); if (!isAgain) { break; } } } }
运行结果如下:
三、查询一组文件夹中的总字节数
此示例演示如何检索指定文件夹及其所有子文件夹中的所有文件所使用的总字节数。
Sum方法添加在select子句中选择的所有项的值。您可以轻松修改此查询以检索指定目录树中的最大或最小文件,方法是调用Min<TSource>或
Max<TSource>方法,而不是Sum。
class Program { static void Main(string[] args) { #region LINQ 查询一组文件夹中的总字节数 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); var query = from file in files select file.Length; //缓存结果,以避免多次访问文件系统 var fileLengths = query as long[] ?? query.ToArray(); //返回最大文件的大小 var largestLength = fileLengths.Max(); //返回指定文件夹下的所有文件中的总字节数 var totalBytes = fileLengths.Sum(); Console.WriteLine(); Console.WriteLine("There are {0} bytes in {1} files under {2}", totalBytes, files.Count(), path); Console.WriteLine("The largest files is {0} bytes.", largestLength); Console.Read(); #endregion } }
运行结果如下:
四、比较两个文件夹中的内容
此示例演示比较两个文件列表的三种方法:
1、查询一个指定两个文件列表是否相同的布尔值。
2、查询用于检索同时位于两个文件夹中的文件的交集。
3、查询用于检索位于一个文件夹中但不在另一个文件夹中的文件的差集。
/// <summary> /// 文件名和字节数比较类 /// </summary> public class FileComparer : IEqualityComparer<FileInfo> { public bool Equals(FileInfo x, FileInfo y) { return string.Equals(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase) && x.Length == y.Length; } //返回一个比较标准的哈希值。根据IEqualityComparer规则,如果相等,那么哈希值也必须是相等的。 //因为这里所定义的相等只是一个简单的值相等,而不是引用标识,所以两个或多个对象将产生相同的哈希值是可能的。 public int GetHashCode(FileInfo obj) { var s = string.Format("{0}{1}", obj.Name, obj.Length); return s.GetHashCode(); } } class Program { static void Main(string[] args) { #region LINQ 查询一组文件夹中的总字节数 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); var query = from file in files select file.Length; //缓存结果,以避免多次访问文件系统 var fileLengths = query as long[] ?? query.ToArray(); //返回最大文件的大小 var largestLength = fileLengths.Max(); //返回指定文件夹下的所有文件中的总字节数 var totalBytes = fileLengths.Sum(); Console.WriteLine(); Console.WriteLine("There are {0} bytes in {1} files under {2}", totalBytes, files.Count(), path); Console.WriteLine("The largest files is {0} bytes.", largestLength); Console.Read(); #endregion } }
运行结果如下:
此处显示的FileComparer类演示如何将自定义比较器类与标准查询运算符一起使用。该类不是为在实际方案中使用而设计的,它只是使用每个
文件的名称和长度(以字节为单位)来确定每个文件夹的内容是否相同。在实际方案中,应对此比较器进行修改以执行更严格的相等性检查。
五、在目录树中查询最大的文件
此示例演示与文件大小(以字节为单位)相关的五种查询:
1、如何检索最大文件的大小(以字节为单位)。
2、如何检索最小文件的大小(以字节为单位)。
3、如何从指定的根文件夹下的一个或多个文件夹检索FileInfo对象最大或最小文件。
4、如何检索一个序列,如10个最大文件。
下面的示例包含五种不同的查询,这些查询演示如何根据文件大小(以字节为单位)查询和分组文件。可以轻松地修改这些示例,以使查询基
于FileInfo对象的某个其他属性。
class Program { static void Main(string[] args) { #region LINQ 在目录树中查询最大的文件 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); var query1 = from file in files select file.Length; //返回最大文件的大小 var maxSize = query1.Max(); Console.WriteLine("The length of the largest file under {0} is {1}", path, maxSize); Console.WriteLine(); //倒序排列 var query2 = from file in files let len = file.Length where len > 0 orderby len descending select file; var fileInfos = query2 as FileInfo[] ?? query2.ToArray(); //倒序排列的第一个就是最大的文件 var longestFile = fileInfos.First(); //倒序排列的第一个就是最小的文件 var smallestFile = fileInfos.Last(); Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes", path, longestFile.FullName, longestFile.Length); Console.WriteLine(); Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes", path, smallestFile.FullName, smallestFile.Length); Console.WriteLine(); Console.WriteLine("===== The 10 largest files under {0} are: =====", path); //返回前10个最大的文件 var queryTenLargest = fileInfos.Take(10); foreach (var file in queryTenLargest) { Console.WriteLine("{0}: {1} bytes", file.FullName, file.Length); } Console.Read(); #endregion } }
运行结果如下:
若要返回一个或多个完整的FileInfo对象,查询必须首先检查数据源中的每个对象,然后按这些对象的Length属性的值排序它们,这样就可以
返回具有最大长度的单个对象或序列。使用 First<TSource>可返回列表中的第一个元素使用 Take<TSource>可返回前n个元素。
六、在目录树中查询重复的文件
有时,多个文件夹中可能存在同名的文件。例如,在Visual Studio安装文件夹中,有多个文件夹包含readme.htm文件。
此示例演示如何在指定的根文件夹中查询重复文件名。
class Program { static void Main(string[] args) { #region LINQ 在目录树中查询重复的文件示例一 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); var charsToSkip = path.Length; var queryDupNames = (from file in files group file.FullName.Substring(charsToSkip) by file.Name into fileGroup where fileGroup.Count() > 1 select fileGroup).Distinct(); PageOutput(queryDupNames); #endregion } /// <summary> /// 分页输出 /// </summary> /// <typeparam name="TK"></typeparam> /// <typeparam name="TV"></typeparam> /// <param name="queryDupNames"></param> private static void PageOutput<TK, TV>(IEnumerable<IGrouping<TK, TV>> queryDupNames) { //控制台输出的高度 var numLines = Console.WindowHeight - 3; var dupNames = queryDupNames as IGrouping<TK, TV>[] ?? queryDupNames.ToArray(); foreach (var queryDupName in dupNames) { //分页开始 var currentLine = 0; do { Console.Clear(); Console.WriteLine("Filename = {0}", queryDupName.Key.ToString() == string.Empty ? "[none]" : queryDupName.Key.ToString()); //跳过currentLine行,取numLines行。 var resultPage = queryDupName.Skip(currentLine).Take(numLines); foreach (var fileName in resultPage) { Console.WriteLine("\t{0}", fileName); } //增量器记录已显示的行数 currentLine += numLines; //按得有点累,还是让它自动下一页吧。 Thread.Sleep(100); } while (currentLine < queryDupName.Count()); } } }
运行结果如下:
此示例演示如何查询其大小和创建时间也匹配的文件。
/// <summary> /// PortableKey类 /// </summary> public class PortableKey { public string Name { get; set; } public DateTime CreationTime { get; set; } public double Length { get; set; } } class Program { static void Main(string[] args) { #region LINQ 在目录树中查询重复的文件示例二 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); var charsToSkip = path.Length; //注意一个复合键的使用,三个属性都匹配的文件属于同一组。 //匿名类型也可以用于复合键,但不能跨越方法边界。 var queryDupFiles = from file in files group file.FullName.Substring(charsToSkip) by new PortableKey() { Name = file.Name, CreationTime = file.CreationTime, Length = file.Length } into fileGroup where fileGroup.Count() > 1 select fileGroup; var queryDupNames = queryDupFiles as IGrouping<PortableKey, string>[] ?? queryDupFiles.ToArray(); var list = queryDupNames.ToList(); var count = queryDupNames.Count(); //分页输出 PageOutput(queryDupNames); Console.Read(); #endregion } /// <summary> /// 分页输出 /// </summary> /// <typeparam name="TK"></typeparam> /// <typeparam name="TV"></typeparam> /// <param name="queryDupNames"></param> private static void PageOutput<TK, TV>(IEnumerable<IGrouping<TK, TV>> queryDupNames) { //控制台输出的高度 var numLines = Console.WindowHeight - 3; var dupNames = queryDupNames as IGrouping<TK, TV>[] ?? queryDupNames.ToArray(); foreach (var queryDupName in dupNames) { //分页开始 var currentLine = 0; do { Console.Clear(); Console.WriteLine("Filename = {0}", queryDupName.Key.ToString() == string.Empty ? "[none]" : queryDupName.Key.ToString()); //跳过currentLine行,取numLines行。 var resultPage = queryDupName.Skip(currentLine).Take(numLines); foreach (var fileName in resultPage) { Console.WriteLine("\t{0}", fileName); } //增量器记录已显示的行数 currentLine += numLines; //按得有点累,还是让它自动下一页吧。 Thread.Sleep(100); } while (currentLine < queryDupName.Count()); } } }
七、在文件夹中查询文件的内容
此示例演示如何查询指定目录树中的所有文件、打开每个文件并检查其内容。 此类技术可用于对目录树的内容创建索引或反向索引。 此示例
虽然执行的是简单的字符串搜索,但是,可使用正则表达式执行更复杂类型的模式匹配。
class Program { static void Main(string[] args) { #region LINQ 在文件夹中查询文件的内容 const string path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\"; var dir = new DirectoryInfo(path); var files = dir.GetFiles("*.*", SearchOption.AllDirectories); //待匹配的字符串 const string searchTerm = @"Visual Studio"; //搜索每个文件的内容。 //您也可以使用正则表达式替换Contains方法 var queryMatchingFiles = from file in files where file.Extension == ".html" let content = GetFileConetnt(file.FullName) where content.Contains(searchTerm) select file.FullName; //执行查询 Console.WriteLine("The term \"{0}\" was found in:", searchTerm); foreach (var filename in queryMatchingFiles) { Console.WriteLine(filename); } Console.Read(); #endregion } /// <summary> /// 读取文件的所有内容 /// </summary> /// <param name="fileName"></param> /// <returns></returns> static string GetFileConetnt(string fileName) { //如果我们在快照后已删除该文件,则忽略它,并返回空字符串。 return File.Exists(fileName) ? File.ReadAllText(fileName) : ""; } }
运行结果如下: