여러 기준으로 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.