您可以使用多个过滤器调用 Directory.GetFiles() 吗?
Posted
技术标签:
【中文标题】您可以使用多个过滤器调用 Directory.GetFiles() 吗?【英文标题】:Can you call Directory.GetFiles() with multiple filters? 【发布时间】:2010-09-14 20:35:34 【问题描述】:我正在尝试使用Directory.GetFiles()
方法检索多种类型的文件列表,例如mp3
和jpg
。我尝试了以下两种方法都没有运气:
Directory.GetFiles("C:\\path", "*.mp3|*.jpg", SearchOption.AllDirectories);
Directory.GetFiles("C:\\path", "*.mp3;*.jpg", SearchOption.AllDirectories);
有没有办法在一次通话中做到这一点?
【问题讨论】:
附带说明,使用 GetFiles 搜索模式过滤扩展名是不安全的。例如,您有两个文件 Test1.xls 和 Test2.xlsx 并且您想使用搜索模式过滤掉 xls 文件 * .xls,但 GetFiles 返回 Test1.xls 和 Test2.xlsx 。 Read Note Section for more info 那么如何预防呢? @kiran 这怎么不安全?这看起来像是一个特性而不是一个错误。 那么如何预防呢?使用 ?.xls 将仅正确过滤 xls 文件,并且不会包含例如 xlsx 文件。 【参考方案1】:这是获取过滤文件的一种简单而优雅的方法
var allowedFileExtensions = ".csv,.txt";
var files = Directory.EnumerateFiles(@"C:\MyFolder", "*.*", SearchOption.TopDirectoryOnly)
.Where(s => allowedFileExtensions.IndexOf(Path.GetExtension(s)) > -1).ToArray();
【讨论】:
此代码的一个问题:如果您在 allowedFileExtensions 中有 .xlsx,它将匹配 .xlsx 以及 .xls 和 .xl 和 .x【参考方案2】:使用 GetFiles 搜索模式过滤扩展是不安全的! 例如,您有两个文件 Test1.xls 和 Test2.xlsx,并且您想使用搜索模式 *.xls 过滤掉 xls 文件,但 GetFiles 返回 Test1.xls 和 Test2.xlsx 当一些临时文件突然被作为正确的文件处理时,我没有意识到这一点并且在生产环境中出错。搜索模式为 *.txt,临时文件名为 *.txt20181028_100753898 因此搜索模式不可信,您还必须对文件名添加额外检查。
【讨论】:
不回答问题。 备注部分这么说 - docs.microsoft.com/en-us/dotnet/api/…【参考方案3】:如果您使用的是 VB.NET(或将依赖项导入到您的 C# 项目中),实际上存在一种方便的方法,可以过滤多个扩展:
Microsoft.VisualBasic.FileIO.FileSystem.GetFiles("C:\\path", Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories, new string[] "*.mp3", "*.jpg");
在 VB.NET 中,这可以通过 My-namespace 访问:
My.Computer.FileSystem.GetFiles("C:\path", FileIO.SearchOption.SearchAllSubDirectories, "*.mp3", "*.jpg")
不幸的是,这些便捷方法不支持像 Directory.EnumerateFiles()
这样的惰性求值变体。
【讨论】:
这很容易成为最好的答案,但更老套的是被接受的答案。一定很喜欢。【参考方案4】:我不知道哪种解决方案更好,但我使用这个:
String[] ext = "*.ext1|*.ext2".Split('|');
List<String> files = new List<String>();
foreach (String tmp in ext)
files.AddRange(Directory.GetFiles(dir, tmp, SearchOption.AllDirectories));
【讨论】:
【参考方案5】:或者您可以将扩展字符串转换为字符串^
vector <string> extensions = "*.mp4", "*.avi", "*.flv" ;
for (int i = 0; i < extensions.size(); ++i)
String^ ext = gcnew String(extensions[i].c_str());;
String^ path = "C:\\Users\\Eric\\Videos";
array<String^>^files = Directory::GetFiles(path,ext);
Console::WriteLine(ext);
cout << " " << (files->Length) << endl;
【讨论】:
这是 c++ 而不是 c#【参考方案6】:怎么样
string[] filesPNG = Directory.GetFiles(path, "*.png");
string[] filesJPG = Directory.GetFiles(path, "*.jpg");
string[] filesJPEG = Directory.GetFiles(path, "*.jpeg");
int totalArraySizeAll = filesPNG.Length + filesJPG.Length + filesJPEG.Length;
List<string> filesAll = new List<string>(totalArraySizeAll);
filesAll.AddRange(filesPNG);
filesAll.AddRange(filesJPG);
filesAll.AddRange(filesJPEG);
【讨论】:
【参考方案7】:DirectoryInfo directory = new DirectoryInfo(Server.MapPath("~/Contents/"));
//Using Union
FileInfo[] files = directory.GetFiles("*.xlsx")
.Union(directory
.GetFiles("*.csv"))
.ToArray();
【讨论】:
如何查看files
以获取路径?谢谢【参考方案8】:
在 .NET 2.0(无 Linq)中:
public static List<string> GetFilez(string path, System.IO.SearchOption opt, params string[] patterns)
List<string> filez = new List<string>();
foreach (string pattern in patterns)
filez.AddRange(
System.IO.Directory.GetFiles(path, pattern, opt)
);
// filez.Sort(); // Optional
return filez; // Optional: .ToArray()
然后使用它:
foreach (string fn in GetFilez(path
, System.IO.SearchOption.AllDirectories
, "*.xml", "*.xml.rels", "*.rels"))
【讨论】:
【参考方案9】:为
var exts = new[] "mp3", "jpg" ;
你可以:
public IEnumerable<string> FilterFiles(string path, params string[] exts)
return
Directory
.EnumerateFiles(path, "*.*")
.Where(file => exts.Any(x => file.EndsWith(x, StringComparison.OrdinalIgnoreCase)));
不要忘记新的 .NET4 Directory.EnumerateFiles
以提高性能 (What is the difference between Directory.EnumerateFiles vs Directory.GetFiles?)
“IgnoreCase”应该比“ToLower”快(.EndsWith("aspx", StringComparison.OrdinalIgnoreCase)
而不是.ToLower().EndsWith("aspx")
)
但是当您拆分过滤器并合并结果时,EnumerateFiles
的真正好处就会显现出来:
public IEnumerable<string> FilterFiles(string path, params string[] exts)
return
exts.Select(x => "*." + x) // turn into globs
.SelectMany(x =>
Directory.EnumerateFiles(path, x)
);
如果您不必将它们变成 glob(即 exts = new[] "*.mp3", "*.jpg"
已经),它会变得更快。
基于以下 LinqPad 测试的性能评估(注意:Perf
只是重复委托 10000 次)
https://gist.github.com/zaus/7454021
(从“重复”重新发布和扩展,因为该问题明确要求没有 LINQ:Multiple file-extensions searchPattern for System.IO.Directory.GetFiles)
【讨论】:
您所说的“如果您不必将它们变成 glob,我会变得更快一些”是什么意思?是 O(1) 还是 O(n)(关于文件数量,而不是扩展名数量)?我猜它是 O(1)(或 O(n) 就扩展数量而言)并且可能在几个 cpu 周期的范围内......如果是这样的话,它可能 - 性能方面 - 可以忽略不计跨度> @BatteryBackupUnit 是的,10k reps against 2 extensions glob 与 str 的差异是 3ms,所以是的,在技术上可以忽略不计(请参阅 perf 结果链接),但不知道您需要过滤多少个扩展名值得指出的是,这是有区别的;我让你来决定“简化用法”(即.FilterFiles(path, "jpg", "gif")
)是否比“显式全局”(即.FilterFiles(path, "*.jpg", "*.gif")
)更好。
完美,谢谢。抱歉,我不知何故跳过了那个 github 链接。也许我应该调整我的屏幕颜色设置:)
SelectMany 解决方案的缺陷是它会在每个传入的文件扩展名中遍历所有文件。
EnumerateFiles
有一个陷阱,其中匹配 *.htm
之类的 3 字符扩展名也将匹配 *.html
或实际上包含 .htm 的任何内容(例如 .htmfoo
)。这似乎与旧的 DOS 8.3 文件名有关。 codeproject.com/Questions/152289/… 在此处查看有关 searchPattern 的说明:docs.microsoft.com/en-us/dotnet/api/…【参考方案10】:
对于 .NET 4.0 及更高版本,
var files = Directory.EnumerateFiles("C:\\path", "*.*", SearchOption.AllDirectories)
.Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg"));
对于早期版本的 .NET,
var files = Directory.GetFiles("C:\\path", "*.*", SearchOption.AllDirectories)
.Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg"));
编辑: 请阅读 cmets。 Paul Farry 提出的改进,以及Christian.K 指出的内存/性能问题都非常重要。
【讨论】:
不过,请确保您了解其中的含义:这将返回字符串数组中的 all 文件,然后按您指定的扩展名对其进行过滤。如果“C:\Path”下面没有很多文件,这可能不是大问题,但可能是“C:\”或类似的内存/性能问题。 ... 2 年后:不错的代码,但要注意这一点,如果你有一个以 .JPG 结尾的文件,它就不会成功。最好加s.ToLower().Endswith...
你可以使用s.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)
请注意,在 .NET 4.0 中,您可以将 Directory.GetFiles
替换为 Directory.EnumerateFiles
、msdn.microsoft.com/en-us/library/dd383571.aspx,这将避免 @Christian.K 提到的内存问题。
如果你想在许多可能的扩展的情况下进一步提高性能,最好创建一个包含所有扩展的 HashSet 并执行Where(f => _validExtensions.Contains(Path.GetExtension(f).ToLower()))
。基本上Contains
比多次执行字符串比较要快得多。【参考方案11】:
不知道为什么贴这么多“解决方案”?
如果我的菜鸟对 GetFiles 工作原理的理解是正确的,那么只有两种选择,上述任何解决方案都可以归结为以下几点:
GetFiles,然后过滤:速度很快,但由于在应用过滤器之前的存储开销导致内存杀手
GetFiles 时过滤:设置的过滤器越多越慢,但内存使用率低,因为不存储任何开销。上述帖子中有一个令人印象深刻的基准对此进行了解释:每个过滤器选项导致单独的 GetFile 操作,因此硬盘的同一部分被读取多次。
在我看来,选项 1) 更好,但在 C:\ 等文件夹上使用 SearchOption.AllDirectories 会占用大量内存。 因此,我将使用选项 1) 制作一个遍历所有子文件夹的递归子方法
这应该会导致每个文件夹上只有 1 次 GetFiles 操作,因此速度很快(选项 1),但只使用少量内存,因为在每个子文件夹读取后应用过滤器 -> 每个子文件夹后删除开销。
如果我错了,请纠正我。正如我所说的那样,我对编程很陌生,但希望对事物有更深入的了解,最终变得擅长这一点:)
【讨论】:
【参考方案12】:还有一个似乎没有任何内存或性能开销并且相当优雅的下降解决方案:
string[] filters = new[]"*.jpg", "*.png", "*.gif";
string[] filePaths = filters.SelectMany(f => Directory.GetFiles(basePath, f)).ToArray();
【讨论】:
我想我可以编辑它,这样它就可以接受未知的无限数量的扩展,带有新的字符串变量和拆分函数。但即便如此,这比 jnoreiga 的解决方案更好吗?它更快吗?内存消耗少? 有一个权衡。这种方法多次调用 GetFiles,每个过滤器调用一次。在某些情况下,这些多次调用可能会产生重大的“性能开销”,它确实具有重要的优势,即每个 GetFiles 仅返回具有 匹配 文件路径的数组。我希望这通常是一个很好的性能结果,甚至可能优越性能,但这需要进行测试。如果 GetFiles 比 EnumerateFiles 快得多,那么这可能是最好的方法。另请注意,当 IEnumerable 可以直接使用时,最后的“.ToArray()”可以省略。【参考方案13】:我遇到了同样的问题,找不到合适的解决方案,所以我编写了一个名为 GetFiles 的函数:
/// <summary>
/// Get all files with a specific extension
/// </summary>
/// <param name="extensionsToCompare">string list of all the extensions</param>
/// <param name="Location">string of the location</param>
/// <returns>array of all the files with the specific extensions</returns>
public string[] GetFiles(List<string> extensionsToCompare, string Location)
List<string> files = new List<string>();
foreach (string file in Directory.GetFiles(Location))
if (extensionsToCompare.Contains(file.Substring(file.IndexOf('.')+1).ToLower())) files.Add(file);
files.Sort();
return files.ToArray();
这个函数只会调用一次Directory.Getfiles()
。
例如这样调用函数:
string[] images = GetFiles(new List<string>"jpg", "png", "gif", "imageFolder");
编辑:要获得一个具有多个扩展名的文件,请使用这个:
/// <summary>
/// Get the file with a specific name and extension
/// </summary>
/// <param name="filename">the name of the file to find</param>
/// <param name="extensionsToCompare">string list of all the extensions</param>
/// <param name="Location">string of the location</param>
/// <returns>file with the requested filename</returns>
public string GetFile( string filename, List<string> extensionsToCompare, string Location)
foreach (string file in Directory.GetFiles(Location))
if (extensionsToCompare.Contains(file.Substring(file.IndexOf('.') + 1).ToLower()) &&& file.Substring(Location.Length + 1, (file.IndexOf('.') - (Location.Length + 1))).ToLower() == filename)
return file;
return "";
例如这样调用函数:
string image = GetFile("imagename", new List<string>"jpg", "png", "gif", "imageFolder");
【讨论】:
【参考方案14】:我知道这是老问题,但 LINQ: (.NET40+)
var files = Directory.GetFiles("path_to_files").Where(file => Regex.IsMatch(file, @"^.+\.(wav|mp3|txt)$"));
【讨论】:
好主意。考虑使用file.ToLower()
轻松匹配大写扩展。为什么不先提取扩展名,这样 Regex 就不必检查整个路径:Regex.IsMatch(Path.GetExtension(file).ToLower(), @"\.(wav|mp3|txt)");
【参考方案15】:
我不能使用.Where
方法,因为我在 .NET Framework 2.0 中编程(Linq 仅在 .NET Framework 3.5+ 中受支持)。
下面的代码不区分大小写(所以.CaB
或.cab
也会被列出)。
string[] ext = new string[2] "*.CAB", "*.MSU" ;
foreach (string found in ext)
string[] extracted = Directory.GetFiles("C:\\test", found, System.IO.SearchOption.AllDirectories);
foreach (string file in extracted)
Console.WriteLine(file);
【讨论】:
【参考方案16】:List<string> FileList = new List<string>();
DirectoryInfo di = new DirectoryInfo("C:\\DirName");
IEnumerable<FileInfo> fileList = di.GetFiles("*.*");
//Create the query
IEnumerable<FileInfo> fileQuery = from file in fileList
where (file.Extension.ToLower() == ".jpg" || file.Extension.ToLower() == ".png")
orderby file.LastWriteTime
select file;
foreach (System.IO.FileInfo fi in fileQuery)
fi.Attributes = FileAttributes.Normal;
FileList.Add(fi.FullName);
【讨论】:
file.Extension.ToLower()
是不好的做法。
那我们应该用什么? @abatishchev
@Nitin: String.Equals(a, b, StringComparison.OrdinalIgnoreCase)
其实 file.Extension.Equals(".jpg",StringComparison.OrdinalIgnoreCase) 是我更喜欢的。它似乎比 .ToLower 或 .ToUpper 快,或者他们在我搜索的任何地方都这么说。实际上,.Equals 也比 == 快,因为 == 调用 .Equals 并且 检查 null(因为你不能执行 null.Equals(null))。【参考方案17】:
让
var set = new HashSet<string> ".mp3", ".jpg" ;
然后
Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
.Where(f => set.Contains(
new FileInfo(f).Extension,
StringComparer.OrdinalIgnoreCase));
或
from file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
from ext in set
where String.Equals(ext, new FileInfo(file).Extension, StringComparison.OrdinalIgnoreCase)
select file;
【讨论】:
getfiles 没有你发布的重载。【参考方案18】:不...我相信您必须根据所需的文件类型进行尽可能多的调用。
我会自己创建一个函数,在带有我需要的扩展的字符串上获取一个数组,然后在该数组上进行迭代,进行所有必要的调用。该函数将返回与我发送的扩展名匹配的文件的通用列表。
希望对你有帮助。
【讨论】:
【参考方案19】:另一种使用 Linq 的方式,但不必返回所有内容并在内存中对其进行过滤。
var files = Directory.GetFiles("C:\\path", "*.mp3", SearchOption.AllDirectories).Union(Directory.GetFiles("C:\\path", "*.jpg", SearchOption.AllDirectories));
实际上是对GetFiles()
的两次调用,但我认为这与问题的精神一致,并以一个可枚举的形式返回它们。
【讨论】:
那为什么要使用 Linq?会比使用 List 和 addrange 更快吗? 我不知道什么会更快,也不认为这是一个重要的问题。对于几乎任何您将使用代码来解决此问题的任何地方,性能差异都可以忽略不计。问题应该是什么更具可读性以简化将来代码的可维护性。我认为这是一个合理的答案,因为它放入了一个源代码行,我认为这是问题所需的一部分,必要的调用并清楚地表达了该行的意图。 list 和 addrange 通过多个步骤来完成同一件事会分散注意力。【参考方案20】:/// <summary>
/// Returns the names of files in a specified directories that match the specified patterns using LINQ
/// </summary>
/// <param name="srcDirs">The directories to seach</param>
/// <param name="searchPatterns">the list of search patterns</param>
/// <param name="searchOption"></param>
/// <returns>The list of files that match the specified pattern</returns>
public static string[] GetFilesUsingLINQ(string[] srcDirs,
string[] searchPatterns,
SearchOption searchOption = SearchOption.AllDirectories)
var r = from dir in srcDirs
from searchPattern in searchPatterns
from f in Directory.GetFiles(dir, searchPattern, searchOption)
select f;
return r.ToArray();
【讨论】:
【参考方案21】:将您想要的扩展名设为一个字符串,即“.mp3.jpg.wma.wmf”,然后检查每个文件是否包含您想要的扩展名。 这适用于 .net 2.0,因为它不使用 LINQ。
string myExtensions=".jpg.mp3";
string[] files=System.IO.Directory.GetFiles("C:\myfolder");
foreach(string file in files)
if(myExtensions.ToLower().contains(System.IO.Path.GetExtension(s).ToLower()))
//this file has passed, do something with this file
这种方法的优点是您可以在不编辑代码的情况下添加或删除扩展,即添加 png 图像,只需编写 myExtensions=".jpg.mp3.png"。
【讨论】:
它不知道s
是什么【参考方案22】:
这个怎么样:
private static string[] GetFiles(string sourceFolder, string filters, System.IO.SearchOption searchOption)
return filters.Split('|').SelectMany(filter => System.IO.Directory.GetFiles(sourceFolder, filter, searchOption)).ToArray();
我在这里(在 cmets 中)找到了它:http://msdn.microsoft.com/en-us/library/wz42302f.aspx
【讨论】:
我猜这避免了评分最高的答案的潜在记忆陷阱?在这种情况下,它应该被评为更高! @DanW 评价最高的答案肯定会给内存带来负担,但我认为这不应该是一个问题。我也喜欢这个答案,但它实际上比接受的答案慢得多。检查这个SpeedTest 谢谢。很高兴看到它的速度只有大约两倍 - 我想我会一直坚持下去。 如果只有两个扩展,它只会慢两倍。如果你有一个 X 扩展列表,那么它会慢 X 倍。因为在这里您多次调用函数 Directory.GetFiles,而在其他解决方案中它只被调用一次。 @OscarHermosilla 可以使用Parallel.ForEach
让它们并行【参考方案23】:
如果您有大量扩展程序需要检查,您可以使用以下内容。我不想创建很多 OR 语句,所以我修改了 lette 写的内容。
string supportedExtensions = "*.jpg,*.gif,*.png,*.bmp,*.jpe,*.jpeg,*.wmf,*.emf,*.xbm,*.ico,*.eps,*.tif,*.tiff,*.g01,*.g02,*.g03,*.g04,*.g05,*.g06,*.g07,*.g08";
foreach (string imageFile in Directory.GetFiles(_tempDirectory, "*.*", SearchOption.AllDirectories).Where(s => supportedExtensions.Contains(Path.GetExtension(s).ToLower())))
//do work here
【讨论】:
请帮帮我...当我打印 imageFile 时,它给出了它的总路径。我怎样才能将它缩小为文件名。 System.IO.Path.GetFileName(imageFile)Path.GetExtension
返回 '.ext',而不是 '*.ext'(至少在 3.5+ 中)。
仅供参考:您需要 System.Linq for .where(
存在潜在缺陷。我们早已过了要求扩展名正好是三个字符的时代。假设您可能会遇到带有.abc
的文件,并且supportedExtensions 包含.abcd
。会匹配,但不应该匹配。修复:supportedExtensions = ".jpg|.abcd|";
和 .Contains(Path.GetExtension(s).ToLower() + "|")
。也就是说,在测试中包含您的分隔符。重要提示:您的分隔符也必须在supportedExceptions 中的最后一个条目之后。【参考方案24】:
以下函数搜索多个模式,以逗号分隔。您还可以指定排除项,例如:“!web.config”将搜索所有文件并排除“web.config”。模式可以混合使用。
private string[] FindFiles(string directory, string filters, SearchOption searchOption)
if (!Directory.Exists(directory)) return new string[] ;
var include = (from filter in filters.Split(new char[] ',' , StringSplitOptions.RemoveEmptyEntries) where !string.IsNullOrEmpty(filter.Trim()) select filter.Trim());
var exclude = (from filter in include where filter.Contains(@"!") select filter);
include = include.Except(exclude);
if (include.Count() == 0) include = new string[] "*" ;
var rxfilters = from filter in exclude select string.Format("^0$", filter.Replace("!", "").Replace(".", @"\.").Replace("*", ".*").Replace("?", "."));
Regex regex = new Regex(string.Join("|", rxfilters.ToArray()));
List<Thread> workers = new List<Thread>();
List<string> files = new List<string>();
foreach (string filter in include)
Thread worker = new Thread(
new ThreadStart(
delegate
string[] allfiles = Directory.GetFiles(directory, filter, searchOption);
if (exclude.Count() > 0)
lock (files)
files.AddRange(allfiles.Where(p => !regex.Match(p).Success));
else
lock (files)
files.AddRange(allfiles);
));
workers.Add(worker);
worker.Start();
foreach (Thread worker in workers)
worker.Join();
return files.ToArray();
用法:
foreach (string file in FindFiles(@"D:\628.2.11", @"!*.config, !*.js", SearchOption.AllDirectories))
Console.WriteLine(file);
【讨论】:
【参考方案25】:刚刚找到了另一种方法。仍然不是一项操作,而是将其扔掉,看看其他人对此有何看法。
private void getFiles(string path)
foreach (string s in Array.FindAll(Directory.GetFiles(path, "*", SearchOption.AllDirectories), predicate_FileMatch))
Debug.Print(s);
private bool predicate_FileMatch(string fileName)
if (fileName.EndsWith(".mp3"))
return true;
if (fileName.EndsWith(".jpg"))
return true;
return false;
【讨论】:
【参考方案26】:不。请尝试以下操作:
List<string> _searchPatternList = new List<string>();
...
List<string> fileList = new List<string>();
foreach ( string ext in _searchPatternList )
foreach ( string subFile in Directory.GetFiles( folderName, ext )
fileList.Add( subFile );
// Sort alpabetically
fileList.Sort();
// Add files to the file browser control
foreach ( string fileName in fileList )
...;
取自:http://blogs.msdn.com/markda/archive/2006/04/20/580075.aspx
【讨论】:
以上是关于您可以使用多个过滤器调用 Directory.GetFiles() 吗?的主要内容,如果未能解决你的问题,请参考以下文章