67

C# がディレクトリをスキャンするバックアップ アプリケーションを作成しています。ディレクトリ内のすべてのファイルとサブファイルを取得するために、このようなものを使用する前に:

DirectoryInfo di = new DirectoryInfo("A:\\");
var directories= di.GetFiles("*", SearchOption.AllDirectories);

foreach (FileInfo d in directories)
{
       //Add files to a list so that later they can be compared to see if each file
       // needs to be copid or not
}

唯一の問題は、ファイルにアクセスできず、いくつかのエラーが発生することです。私が得るエラーの例は次のとおりです。エラー

その結果、現在のディレクトリ内のすべてのファイルをスキャンする再帰的な方法を作成しました。そのディレクトリにディレクトリがある場合、そのディレクトリを渡してメソッドが再度呼び出されます。この方法の良いところは、ファイルを try catch ブロック内に配置して、エラーがない場合はそれらのファイルをリストに追加し、エラーがある場合はディレクトリを別のリストに追加するオプションを提供できることです。

try
{
    files = di.GetFiles(searchPattern, SearchOption.TopDirectoryOnly);               
}
catch
{
     //info of this folder was not able to get
     lstFilesErrors.Add(sDir(di));
     return;
}

したがって、この方法はうまく機能しますが、唯一の問題は、大きなディレクトリをスキャンするときに時間がかかることです。どうすればこのプロセスをスピードアップできますか? 私の実際の方法は、必要な場合に備えてこれです。

private void startScan(DirectoryInfo di)
{
    //lstFilesErrors is a list of MyFile objects
    // I created that class because I wanted to store more specific information
    // about a file such as its comparePath name and other properties that I need 
    // in order to compare it with another list

    // lstFiles is a list of MyFile objects that store all the files
    // that are contained in path that I want to scan

    FileInfo[] files = null;
    DirectoryInfo[] directories = null;
    string searchPattern = "*.*";

    try
    {
        files = di.GetFiles(searchPattern, SearchOption.TopDirectoryOnly);               
    }
    catch
    {
        //info of this folder was not able to get
        lstFilesErrors.Add(sDir(di));
        return;
    }

    // if there are files in the directory then add those files to the list
    if (files != null)
    {
        foreach (FileInfo f in files)
        {
            lstFiles.Add(sFile(f));
        }
    }


    try
    {
        directories = di.GetDirectories(searchPattern, SearchOption.TopDirectoryOnly);
    }
    catch
    {
        lstFilesErrors.Add(sDir(di));
        return;
    }

    // if that directory has more directories then add them to the list then 
    // execute this function
    if (directories != null)
        foreach (DirectoryInfo d in directories)
        {
            FileInfo[] subFiles = null;
            DirectoryInfo[] subDir = null;

            bool isThereAnError = false;

            try
            {
                subFiles = d.GetFiles();
                subDir = d.GetDirectories();

            }
            catch
            {
                isThereAnError = true;                                                
            }

            if (isThereAnError)
                lstFilesErrors.Add(sDir(d));
            else
            {
                lstFiles.Add(sDir(d));
                startScan(d);
            }


        }

}

次のような例外を処理しようとすると、問題が発生します。

DirectoryInfo di = new DirectoryInfo("A:\\");
FileInfo[] directories = null;
            try
            {
                directories = di.GetFiles("*", SearchOption.AllDirectories);

            }
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine("There was an error with UnauthorizedAccessException");
            }
            catch
            {
                Console.WriteLine("There was antother error");
            }

例外が発生した場合、ファイルを取得できません。

4

7 に答える 7

46

この方法ははるかに高速です。多数のファイルをディレクトリに配置する場合にのみ、電話をかけることができます。私の A:\ 外付けハード ドライブにはほぼ 1 テラビットが含まれているため、多くのファイルを処理する場合に大きな違いが生じます。

static void Main(string[] args)
{
    DirectoryInfo di = new DirectoryInfo("A:\\");
    FullDirList(di, "*");
    Console.WriteLine("Done");
    Console.Read();
}

static List<FileInfo> files = new List<FileInfo>();  // List that will hold the files and subfiles in path
static List<DirectoryInfo> folders = new List<DirectoryInfo>(); // List that hold direcotries that cannot be accessed
static void FullDirList(DirectoryInfo dir, string searchPattern)
{
    // Console.WriteLine("Directory {0}", dir.FullName);
    // list the files
    try
    {
        foreach (FileInfo f in dir.GetFiles(searchPattern))
        {
            //Console.WriteLine("File {0}", f.FullName);
            files.Add(f);                    
        }
    }
    catch
    {
        Console.WriteLine("Directory {0}  \n could not be accessed!!!!", dir.FullName);                
        return;  // We alredy got an error trying to access dir so dont try to access it again
    }

    // process each directory
    // If I have been able to see the files in the directory I should also be able 
    // to look at its directories so I dont think I should place this in a try catch block
    foreach (DirectoryInfo d in dir.GetDirectories())
    {
        folders.Add(d);
        FullDirList(d, searchPattern);                    
    }

}

ちなみに、あなたのコメントのおかげでこれを手に入れました ジム・ミシェル

于 2011-05-19T17:54:09.600 に答える
19

.NET 4.0 にはDirectory.EnumerateFilesメソッドがありIEnumerable<string>、メモリ内のすべてのファイルをロードするわけではありません。ファイルが返され、例外が処理される可能性があるのは、返されたコレクションの反復処理を開始したときだけです。

于 2011-05-19T17:06:48.597 に答える
14

.NET ファイル列挙メソッドが遅いという長い歴史があります。問題は、大規模なディレクトリ構造を瞬時に列挙する方法がないことです。ここで受け入れられた回答でさえ、GC 割り当てに関する問題があります。

私ができる最善のことは、ライブラリにラップされ、 CSharpTest.Net.IO名前空間でFindFile ( source ) クラスとして公開されたことです。このクラスは、不要な GC 割り当てや文字列マーシャリングを行わずに、ファイルとフォルダーを列挙できます。

使い方は簡単で、RaiseOnAccessDenied プロパティは、ユーザーがアクセスできないディレクトリとファイルをスキップします。

    private static long SizeOf(string directory)
    {
        var fcounter = new CSharpTest.Net.IO.FindFile(directory, "*", true, true, true);
        fcounter.RaiseOnAccessDenied = false;

        long size = 0, total = 0;
        fcounter.FileFound +=
            (o, e) =>
            {
                if (!e.IsDirectory)
                {
                    Interlocked.Increment(ref total);
                    size += e.Length;
                }
            };

        Stopwatch sw = Stopwatch.StartNew();
        fcounter.Find();
        Console.WriteLine("Enumerated {0:n0} files totaling {1:n0} bytes in {2:n3} seconds.",
                          total, size, sw.Elapsed.TotalSeconds);
        return size;
    }

私のローカル C:\ ドライブでは、次のように出力されます。

232.876 秒で合計 307,707,792,662 バイトの 810,046 個のファイルを列挙しました。

走行距離はドライブの速度によって異なる場合がありますが、これはマネージ コードでファイルを列挙する方法として私が見つけた最速の方法です。イベント パラメーターは、 FindFile.FileFoundEventArgs型の変化するクラスであるため、イベントが発生するたびに値が変化するため、参照を保持しないように注意してください。

于 2012-09-17T17:57:30.227 に答える
3

(あなたの他の質問の私の他の答えからこの作品をコピーしました)

ディレクトリ内のすべてのファイルを検索するときに進行状況を表示する

高速ファイル列挙

もちろん、すでにご存じのとおり、列挙自体を行う方法はたくさんありますが、瞬時にできるものはありません。ファイル システムのUSN ジャーナルを使用してスキャンを実行してみてください。CodePlex でこのプロジェクトを見てみましょう: VB.NET の MFT スキャナー... 私の IDE SATA (SSD ではない) ドライブ内のすべてのファイルが 15 秒以内に見つかり、311000 個のファイルが見つかりました。

探しているパス内のファイルのみが返されるように、ファイルをパスでフィルター処理する必要があります。しかし、それは仕事の簡単な部分です!

于 2012-09-17T17:10:39.800 に答える
3

私はこれが古いことを知っていますが...別のオプションは、次のようにFileSystemWatcherを使用することです。

void SomeMethod()
{
    System.IO.FileSystemWatcher m_Watcher = new System.IO.FileSystemWatcher();
    m_Watcher.Path = path;
    m_Watcher.Filter = "*.*";
    m_Watcher.NotifyFilter = m_Watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
    m_Watcher.Created += new FileSystemEventHandler(OnChanged);
    m_Watcher.EnableRaisingEvents = true;
}

private void OnChanged(object sender, FileSystemEventArgs e)
    {
        string path = e.FullPath;

        lock (listLock)
        {
            pathsToUpload.Add(path);
        }
    }

これにより、非常に軽量なプロセスでディレクトリのファイル変更を監視でき、変更されたファイルの名前を保存して、適切なタイミングでバックアップできるようになります。

于 2012-10-20T00:40:46.770 に答える
2

これを使用して、すべてのディレクトリとサブディレクトリを取得できます。次に、単純にループしてファイルを処理します。

string[] folders = System.IO.Directory.GetDirectories(@"C:\My Sample Path\","*", System.IO.SearchOption.AllDirectories);

foreach(string f in folders)
{
   //call some function to get all files in folder
}
于 2012-05-14T13:29:56.990 に答える