web-dev-qa-db-fra.com

Le moyen le plus rapide en C # de rechercher un fichier dans un répertoire de plus de 20 000 fichiers

J'ai un travail qui s'exécute chaque nuit pour extraire des fichiers XML d'un répertoire contenant plus de 20 000 sous-dossiers sous la racine. Voici à quoi ressemble la structure:

rootFolder/someFolder/someSubFolder/xml/myFile.xml
rootFolder/someFolder/someSubFolder1/xml/myFile1.xml
rootFolder/someFolder/someSubFolderN/xml/myFile2.xml
rootFolder/someFolder1
rootFolder/someFolderN

Donc, en regardant ce qui précède, la structure est toujours la même: un dossier racine, puis deux sous-dossiers, puis un répertoire xml, puis le fichier xml . Seuls le nom du dossier rootFolder et le répertoire xml m sont connus.

Le code ci-dessous parcourt tous les répertoires et est extrêmement lent. Des recommandations sur la manière d'optimiser la recherche, en particulier si la structure de répertoires est connue?

string[] files = Directory.GetFiles(@"\\somenetworkpath\rootFolder", "*.xml", SearchOption.AllDirectories);
18
adeel825

Plutôt que de faire GetFiles et d'effectuer une recherche brutale, vous pouvez probablement utiliser GetDirectories, d'abord pour obtenir une liste du "Premier sous-dossier", parcourez ces répertoires, puis répétez le processus pour le sous-dossier, parcourez-les, regardez pour le dossier xml, et enfin la recherche de fichiers .xml.

Maintenant, en ce qui concerne les performances, la vitesse varie, mais la recherche de répertoires en premier lieu ALORS accéder aux fichiers devrait beaucoup vous aider!

Mettre à jour

Ok, j’ai fait quelques essais et vous pouvez l’optimiser beaucoup plus loin que je ne le pensais.

L'extrait de code suivant va rechercher une structure de répertoire et rechercher TOUS les dossiers "xml" dans toute l'arborescence.

string startPath = @"C:\Testing\Testing\bin\Debug";
string[] oDirectories = Directory.GetDirectories(startPath, "xml", SearchOption.AllDirectories);
Console.WriteLine(oDirectories.Length.ToString());
foreach (string oCurrent in oDirectories)
    Console.WriteLine(oCurrent);
Console.ReadLine();

Si vous déposez cela dans une application de console de test, vous verrez apparaître les résultats.

Maintenant, une fois que vous avez cela, il suffit de regarder dans chacun des répertoires trouvés pour vous fichiers .xml.

16
Mitchel Sellers

J'ai créé une méthode récursive GetFolders en utilisant un Parallel.ForEach pour rechercher tous les dossiers nommés en tant que variable yourKeyword

List<string> returnFolders = new List<string>();
object locker = new object();

Parallel.ForEach(subFolders, subFolder =>
{
    if (subFolder.ToUpper().EndsWith(yourKeyword))
    {
        lock (locker)
        {
            returnFolders.Add(subFolder);
        }
    }
    else
    {
        lock (locker)
        {
            returnFolders.AddRange(GetFolders(Directory.GetDirectories(subFolder)));
        }
    }
});

return returnFolders;
6
ceffoh

Existe-t-il des répertoires supplémentaires au même niveau que le dossier XML? Si tel est le cas, vous pouvez probablement accélérer la recherche si vous le faites vous-même et éliminer ce niveau de recherche.

        System.IO.DirectoryInfo root = new System.IO.DirectoryInfo(rootPath);
        List<System.IO.FileInfo> xmlFiles=new List<System.IO.FileInfo>();

        foreach (System.IO.DirectoryInfo subDir1 in root.GetDirectories())
        {
            foreach (System.IO.DirectoryInfo subDir2 in subDir1.GetDirectories())
            {
                System.IO.DirectoryInfo xmlDir = new System.IO.DirectoryInfo(System.IO.Path.Combine(subDir2.FullName, "xml"));

                if (xmlDir.Exists)
                {
                    xmlFiles.AddRange(xmlDir.GetFiles("*.xml"));
                }
            }
        }
3
Adam Robinson

Selon vos besoins et votre configuration, vous pouvez utiliser l'indice de recherche Windows: https://msdn.Microsoft.com/en-us/library/windows/desktop/bb266517(v=vs.85).aspx

En fonction de votre configuration, cela pourrait considérablement améliorer les performances.

0
Henrik Gering

Pour ceux d'entre vous qui veulent rechercher un seul fichier et vous connaissez votre répertoire racine, je vous suggère de le garder aussi simple que possible. Cette approche a fonctionné pour moi.

    private void btnSearch_Click(object sender, EventArgs e)
    {
        string userinput = txtInput.Text;

        string sourceFolder = @"C:\mytestDir\";
        string searchWord = txtInput.Text + ".pdf";
        string filePresentCK = sourceFolder + searchWord;
        if (File.Exists(filePresentCK))
            {

                pdfViewer1.LoadFromFile(sourceFolder+searchWord);
            }
            else if(! File.Exists(filePresentCK))
            {
                MessageBox.Show("Unable to Find file :" + searchWord);
            }

        txtInput.Clear();

    }// end of btnSearch method 
0
Wazzie

Je ne vois rien de plus rapide en C #, mais l'indexation est-elle activée pour ce système de fichiers?

0
Chris Doggett

La seule façon pour moi de voir que cela ferait une grande différence est de changer de chasse au poids brut et d’utiliser une routine d’indexation par un tiers ou un système d’exploitation pour accélérer le retour. Ainsi, la recherche est effectuée hors ligne depuis votre application.

Mais je suggérerais également que vous recherchiez de meilleurs moyens de structurer ces données, si possible.

0
Michael

Pour la recherche de fichiers et de répertoires, je souhaiterais utiliser une bibliothèque .NET multithreading qui possède de nombreuses possibilités de recherche . Toutes les informations sur la bibliothèque disponibles sur GitHub: https://github.com/VladPVS/FastSearchLibrary Si vous souhaitez le télécharger, vous pouvez le faire ici: https://github.com/VladPVS/FastSearchLibrary/releases Si vous avez des questions, posez-les.

Travaille très vite. Vérifiez vous-même!

C’est un exemple démonstratif de son utilisation:

class Searcher
{
    private static object locker = new object(); 

    private FileSearcher searcher;

    List<FileInfo> files;

    public Searcher()
    {
        files = new List<FileInfo>();
    }

    public void Startsearch()
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        searcher = new FileSearcher(@"C:\", (f) =>
        {
            return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
        }, tokenSource);  


        searcher.FilesFound += (sender, arg) => 
        {
            lock (locker) // using a lock is obligatorily
            {
                arg.Files.ForEach((f) =>
                {
                    files.Add(f);
                    Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
                });

                if (files.Count >= 10) 
                    searcher.StopSearch();
            }
        };

        searcher.SearchCompleted += (sender, arg) => 
        {
            if (arg.IsCanceled) 
                Console.WriteLine("Search stopped.");
            else
                Console.WriteLine("Search completed.");

            Console.WriteLine($"Quantity of files: {files.Count}"); 
        };

        searcher.StartSearchAsync();
    }
}

Cela fait partie d'un autre exemple:

***
List<string> folders = new List<string>
{
  @"C:\Users\Public",
  @"C:\Windows\System32",
  @"D:\Program Files",
  @"D:\Program Files (x86)"
}; // list of search directories

List<string> keywords = new List<string> { "Word1", "Word2", "Word3" }; // list of search keywords

FileSearcherMultiple multipleSearcher = new FileSearcherMultiple(folders, (f) =>
{
  if (f.CreationTime >= new DateTime(2015, 3, 15) &&
     (f.Extension == ".cs" || f.Extension == ".sln"))
    foreach (var keyword in keywords)
      if (f.Name.Contains(keyword))
        return true;
  return false;
}, tokenSource, ExecuteHandlers.InCurrentTask, true);  
***

De plus, on peut utiliser une méthode statique simple:

List<FileInfo> files = FileSearcher.GetFilesFast(@"C:\Users", "*.xml");

Notez que toutes les méthodes de cette bibliothèque NE LANCENT PAS une exception UnauthorizedAccessException à la place des méthodes de recherche .NET standard.

De plus, les méthodes rapides de cette bibliothèque sont exécutées au moins 2 fois plus rapidement qu'un simple algorithme récursif à un seul thread si vous utilisez un processeur multicœur.

0
VladVS

Utilisez P/Invoke sur la variable FindFirstFile/FindNextFile/FindClose et évitez la surcharge liée à la création de nombreuses instances de FileInfo.

Mais ce sera un travail difficile à faire (vous devrez faire vous-même le traitement du fichier par rapport au répertoire et de la récursion). Essayez donc quelque chose de simple (Directory.GetFiles (), Directory.GetDirectories ()) pour commencer et faire fonctionner les choses. S'il est trop lent, examinez les solutions de rechange (mais mesurez toujours, trop facile de le ralentir).

0
Richard