여러 기준으로 Directory.EnumerateFiles를 필터링하는 방법은 무엇입니까?


79

다음 코드가 있습니다.

List<string> result = new List<string>();

foreach (string file in Directory.EnumerateFiles(path,"*.*",  
      SearchOption.AllDirectories)
      .Where(s => s.EndsWith(".mp3") || s.EndsWith(".wma")))
       {
          result.Add(file);                 
       }

잘 작동하고 필요한 것을 수행합니다. 한 가지 작은 것을 제외하고. 여러 확장을 필터링하는 더 좋은 방법을 찾고 싶습니다. 다음과 같은 필터와 함께 문자열 배열을 사용하고 싶습니다.

string[] extensions = { "*.mp3", "*.wma", "*.mp4", "*.wav" };

NET Framework 4.0 / LINQ를 사용하여이 작업을 수행하는 가장 효율적인 방법은 무엇입니까? 어떤 제안?

가끔 프로그래머가되어 주셔서 감사합니다 :-)


각 확장 검색을 병렬로 실행하는 것을 고려해야합니다. 내 대답에 유용한 도우미 방법을 만들었습니다. 하나는 정규 표현식을 사용하고 다른 하나는 문자열 목록을 사용합니다.
Mikael Svenson

2
이것은 매우 오래된 질문 (이미 @MikaelSvenson에 의해 적절하게 답변 됨)이지만 또 다른 옵션은 다음과 같이 Enumerable 확장자 .Union ()을 사용하는 것입니다. foreach (Directory.EnumerateFiles (path, " .mp3", SearchOption. AllDirectories) .Union (Directory.EnumerateFiles (경로, " .WMA"SearchOption.AllDirectories)) {...}
Kirkaiya

답변:


86

이 문제를 해결하기 위해 올해 초에 블로그올린 몇 가지 도우미 방법을 만들었습니다 .

한 버전은 정규식 패턴 \.mp3|\.mp4을 사용하고 다른 버전은 문자열 목록을 사용하여 병렬로 실행됩니다.

public static class MyDirectory
{   // Regex version
   public static IEnumerable<string> GetFiles(string path, 
                       string searchPatternExpression = "",
                       SearchOption searchOption = SearchOption.TopDirectoryOnly)
   {
      Regex reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase);
      return Directory.EnumerateFiles(path, "*", searchOption)
                      .Where(file =>
                               reSearchPattern.IsMatch(Path.GetExtension(file)));
   }

   // Takes same patterns, and executes in parallel
   public static IEnumerable<string> GetFiles(string path, 
                       string[] searchPatterns, 
                       SearchOption searchOption = SearchOption.TopDirectoryOnly)
   {
      return searchPatterns.AsParallel()
             .SelectMany(searchPattern => 
                    Directory.EnumerateFiles(path, searchPattern, searchOption));
   }
}

좋은 구현에 감사드립니다. 최종적으로 WPF 화면에 결과를 표시하는 좋은 (효율적인) 방법은 무엇입니까? 병렬 방법을 사용하여 파일을 가져올 계획입니다. foreach를 사용하여 결과를 반복하고 목록에 저장 한 다음 화면에로드하면 어떻게됩니까?
Saurabh Kumar 2012

바인딩이 모든 결과를 열거하므로 두 메서드의 출력에 바인딩 할 수 있습니다. 먼저 별도의 목록에 저장할 필요가 없습니다. 가장 효율적인 방법은 항목이 열거 될 때 표시를 시작하는 것입니다. 저는 WPF 전문가는 아니지만 일부 신호를 보내면 항목별로 렌더링 할 수 있어야한다고 생각합니다.
Mikael Svenson 2011

훌륭한 예! 두 가지 방법 각각의 몇 가지 특성에 유의하십시오. 방법을 사용하면 PARALLEL검색이 대소 문자를 구분하지 않으며 결과가 순서가 맞지 않게됩니다. 이 REGEX방법을 사용하면 Searches는 대소 문자를 구분하며 (같은 것을 사용하지 않는 한 "(?i)\.mp3$|\.mp4$") 결과는 예상대로 순서대로 표시됩니다. 테스트를 실행 한 결과 병렬 버전이 약간 더 빠르게 실행될 수 있지만 모두 아주 작은 차이가 있음을 알았습니다.
Arvo Bowen

경우에 민감한 비교 풍어를 @ArvoBowen 및 코드에서 regexoption 추가
미카엘 Svenson

이것은 훌륭한 솔루션입니다. 감사! 참고로, IEnumerable (대부분 내가 몇 군데에서 사용한 Count () 메서드를 중심으로 추적했지만 이것이 유일한 성능 저하는 아니 었습니다)으로 거슬러 올라간 성능 문제에 직면했습니다. 내 목록에는 약 4700 개의 파일 이름이 있습니다. 나는 목록에서 .ToArray ()를 수행하고 모든 것을 배열로 처리했습니다. 당신은 목록을 배열로 바꾸는 일회성 가격을 지불하지만 이후 눈에 띄게 빠른 성능으로 완화되는 것 이상입니다.
Toolsmythe

30

LINQ 컨텍스트에서 제외되어 파일이 확장명 목록과 일치하는지 확인하는 방법이 있습니다. System.IO.Path.GetExtension()여기에서보다 나은 선택 String.EndsWith()입니다. 다중은 ||교체 할 수 있습니다 .Contains()또는 .IndexOf()컬렉션에 따라 달라집니다.

var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)  
   {  ".mp3", ".wma", ".mp4", ".wav" };

...  s => extensions.Contains(Path.GetExtension(s))

2
*문자열 비교를 하려면 s 를 제거해야 합니다.
Gabe

또 다른 제안은 대소 문자를 구분하지 않는 검사를 허용하는 과부하를 사용하는 것입니다.
Dirk Vollmar

1
아마도 해시 셋과 대소 문자를 구분하지 않는 비교를 사용하는 것이 더 낫습니다.
Jim Mischel

2
점 (.mp3)을 포함해야합니다. 대소 문자를 처리하려면 string.ToLower ()를 사용하십시오.
Hans Passant

@Hans, 점에 대해서는 맞지만 ToLower ()가 OrdinalIgnoreCase보다 낫습니까? 선택 : stackoverflow.com/questions/501906/…
Henk Holterman

19

가장 우아한 접근 방식은 다음과 같습니다.

var directory = new DirectoryInfo(path);
var masks = new[] { "*.mp3", "*.wav" };
var files = masks.SelectMany(directory.EnumerateFiles);

그러나 가장 효율적이지 않을 수 있습니다.


하위 폴더를 포함하는 방법은 무엇입니까?
Asen Kasimov

1
@AsenKasimov EnumerateFiles 메서드에는 하위 폴더 내부도 검색하도록 지정할 수있는 매개 변수가있는 오버로드가 있습니다.
Tom Pažourek

18
string path = "C:\\";
var result = new List<string>();
string[] extensions = { ".mp3", ".wma", ".mp4", ".wav" };

foreach (string file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
    .Where(s => extensions.Any(ext => ext == Path.GetExtension(s))))
{
    result.Add(file);
    Console.WriteLine(file);
}

또한 "mp3"가 아닌 ".mp3"가 필요합니다.
Henk Holterman

감사합니다 완벽하게 작동합니다 ... 제 경우에는 .ToArray () 전에 추가해야합니다.이 LINQ 쿼리 없이는 작동하지 않습니다.
equiman

12

주석에서 언급했듯이 Mikael Svenson의 도우미 메서드는 아주 작은 솔루션이지만 일회성 프로젝트를 위해 서둘러 다시 작업을 시도한다면 Linq 확장 .Union ()을 고려하십시오 . 이렇게하면 두 개의 열거 가능한 시퀀스를 결합 할 수 있습니다. 귀하의 경우 코드는 다음과 같습니다.

List<string> result = Directory.EnumerateFiles(path,"*.mp3", SearchOption.AllDirectories)
.Union(Directory.EnumerateFiles(path, ".wma", SearchOption.AllDirectories)).ToList();

이렇게하면 결과 목록이 모두 한 줄로 생성되고 채워집니다.


2
우아하고 C #에 의한 모든 파일의 열거를 방지하여 파일 시스템을 최적화 할 수 있습니다.
크레이그 Brunetti

2

나는 이것이 오래된 게시물이라는 것을 알고 있지만 사람들이 사용하고 싶은 해결책을 생각해 냈습니다.

private IEnumerable<FileInfo> FindFiles()
{
    DirectoryInfo sourceDirectory = new DirectoryInfo(@"C:\temp\mydirectory");
    string foldersFilter = "*bin*,*obj*";
    string fileTypesFilter = "*.mp3,*.wma,*.mp4,*.wav";

    // filter by folder name and extension
    IEnumerable<DirectoryInfo> directories = foldersFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateDirectories(pattern, SearchOption.AllDirectories));
    List<FileInfo> files = new List<FileInfo>();
    files.AddRange(directories.SelectMany(dir => fileTypesFilter.Split(',').SelectMany(pattern => dir.EnumerateFiles(pattern, SearchOption.AllDirectories))));

    // Pick up root files
    files.AddRange(fileTypesFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateFiles(fileTypesFilter, SearchOption.TopDirectoryOnly)));

    // filter just by extension
    IEnumerable<FileInfo> files2 = fileTypesFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateFiles(pattern, SearchOption.AllDirectories));
}

1

GUI 열기 대화 상자와 동일한 파일 확장자 목록 문자열을 사용하는 필터링의 경우 예 :

".exe,.pdb".Split(',', ';', '|').SelectMany(_ => Directory.EnumerateFiles(".", "*" + _, searchOptions)

포장 :

    public static IEnumerable<string> EnumerateFilesFilter(string path, string filesFilter, SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        return filesFilter.Split(',', ';', '|').SelectMany(_ => Directory.EnumerateFiles(path, "*" + _, searchOption));
    }

0

이 문제를 다음과 같이 해결했습니다.

string[] formats = {".mp3", ".wma", ".mp4"};

foreach (var file in Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(x => formats.Any(x.EndsWith)))
{
    // TODO...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.