DOBON.NETプログラミング道掲示板
(現在 過去ログ1 を表示中)

[ 最新記事及び返信フォームをトピックトップへ ]

■32914 / inTopicNo.1)  フォルダ内のファイル数の取得
  
□投稿者/ レイン 一般人(1回)-(2015/04/24(Fri) 10:39:44)
  • アイコン環境/言語:[Windows 7 64Bit/VS2013 Pro/WPF(C#)/.NET Framework 4.5] 
    分類:[.NET] 

    2015/04/28(Tue) 16:25:05 編集(投稿者)
    2015/04/28(Tue) 16:24:49 編集(投稿者)
    
    こんにちは。
    
    指定されたフォルダ内の(サブフォルダ内も含む)ファイル総数を取得しようとしていまして、
    BackgroundWorkerのDoWork内で、
    int total = Directory.GetFiles(FolderName, "*", SearchOption.AllDirectories).Length;
    を実行しています。
    
    コンピュータ起動後最初にこの場所を通るときに約40秒くらい掛かってしまいます。
    次に通るときには1秒くらいで終わります。(キャッシュが効いている?)
    
    テストで使用しているデータはサブフォルダが約4000件、ファイル数が約10000件です。
    OSは7(32・64ビット)、8.1(64ビット)とも同じ状態です。
    実環境ではファイル数は増えます。
    
    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        int total = Directory.GetFiles(FolderName, "*", SearchOption.AllDirectories).Length;
        float f = total / 100;
        int count = 0;
    
        {
            処理。。。
            _backgroundWorker.ReportProgress((++count) / f);
        }
    }
    
    処理に時間が掛かるためにプログレスバーで進行状況が分かるようにしようとしています。
    
    このファイル総数を取得するのを早くする方法はありますでしょうか?
    よろしくお願いいたします。
    
    
    【追記】
    ファイル数を約1万件と書いていましたが、約10万件の間違いです。
    私のミスです。申し訳ございませんでした。

引用返信 削除キー/
■32915 / inTopicNo.2)  Re[1]: フォルダ内のファイル数の取得
□投稿者/ shu 大御所(461回)-(2015/04/24(Fri) 16:56:30)
  • アイコンNo32914に返信(レインさんの記事)

    ファイル総数ではなくサブディレクトリの数に対する進捗にするとか。
引用返信 削除キー/
■32916 / inTopicNo.3)  Re[1]: フォルダ内のファイル数の取得
□投稿者/ 魔界の仮面弁士 大御所(955回)-(2015/04/24(Fri) 19:52:30)
  • アイコンNo32914に返信(レインさんの記事)
    > int total = Directory.GetFiles(FolderName, "*", SearchOption.AllDirectories).Length;

    SearchOption.AllDirectories の利用はお奨めしません。

    下位のフォルダーで読み取りエラー(権限不足など)があると、
    Directory.GetFiles はその読み取りをスキップするのではなく、
    処理全体を失敗させてしまうからです。

    そして何より、全件走査が完了するまで処理が戻りませんよね。



    今回、処理進捗を表示できるようにしたのであれば、
    捜索範囲をディレクトリ直下のファイルのみに限定し、
    子ディレクトリに対して、再帰的に同じ処理を繰り返すことで
    全ファイルを列挙させるのが良いとおもいます。
引用返信 削除キー/
■32917 / inTopicNo.4)  Re[2]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(2回)-(2015/04/27(Mon) 09:20:48)
  • アイコンNo32915に返信(shuさんの記事)

    お返事遅くなりましてすいません。

    > ファイル総数ではなくサブディレクトリの数に対する進捗にするとか。

    int total = Directory.GetDirectories(FolderName, "*", SearchOption.AllDirectories).Length;

    を試してみたのですが、GetFilesの時と同じく時間が掛かってしまいました。

    魔界の仮面弁士さんが書かれているようにSearchOption.AllDirectoriesは、
    この用途ではよくないのですかね?

引用返信 削除キー/
■32918 / inTopicNo.5)  Re[2]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(3回)-(2015/04/27(Mon) 09:48:40)
  • アイコンNo32916に返信(魔界の仮面弁士さんの記事)

    お返事ありがとうございます。

    > 下位のフォルダーで読み取りエラー(権限不足など)があると、
    > Directory.GetFiles はその読み取りをスキップするのではなく、
    > 処理全体を失敗させてしまうからです。

    そういうこともあるのですね。
    自身の管理下にあるフォルダ内の走査なので、
    読み取りエラーなどは今まで起こってませんでした。
    そういったファイルが入り込む可能性もあるかもしれないので、
    その時の処理も付け加えることにします。


    > 捜索範囲をディレクトリ直下のファイルのみに限定し、
    > 子ディレクトリに対して、再帰的に同じ処理を繰り返すことで
    > 全ファイルを列挙させるのが良いとおもいます。

    この方法を試させていただきます。
    これだと、先に書かれていた走査中に戻ってこない問題も解決できそうです。

    また、結果が出ましたら投稿させていただきます。



引用返信 削除キー/
■32919 / inTopicNo.6)  Re[3]: フォルダ内のファイル数の取得
□投稿者/ shu 大御所(462回)-(2015/04/27(Mon) 12:37:44)
  • アイコン
    No32917に返信(レインさんの記事)
    > ■No32915に返信(shuさんの記事)
    > 
    > お返事遅くなりましてすいません。
    > 
    >>ファイル総数ではなくサブディレクトリの数に対する進捗にするとか。
    > 
    > int total = Directory.GetDirectories(FolderName, "*", SearchOption.AllDirectories).Length;
    > 
    > を試してみたのですが、GetFilesの時と同じく時間が掛かってしまいました。
    > 
    > 魔界の仮面弁士さんが書かれているようにSearchOption.AllDirectoriesは、
    > この用途ではよくないのですかね?
    > 
    内容が分かりにくかったようですね。直下のフォルダー数という意味でサブディレクトリと書きました。つまりそのオプションはいりません。
    直下のフォルダを取得して各フォルダに対し今の処理を行うようにしてそのフォルダ配下がすべて完了したらカウント1アップのようなイメージです。
    
    4000フォルダ情報を読むのに時間がかかるようなら総数に対する進捗はあきらめた方がよいと思います。EnumerateDirectories, EnumerateFilesにより順次処理を行うようにすると一括で情報取得をしない分待ち時間を分散出来ると思います。
    
    
    
    
    

引用返信 削除キー/
■32920 / inTopicNo.7)  Re[3]: フォルダ内のファイル数の取得
□投稿者/ 魔界の仮面弁士 大御所(956回)-(2015/04/28(Tue) 12:34:59)
  • アイコンNo32918に返信(レインさんの記事)
    >>捜索範囲をディレクトリ直下のファイルのみに限定し、
    >>子ディレクトリに対して、再帰的に同じ処理を繰り返すことで
    >>全ファイルを列挙させるのが良いとおもいます。
    > これだと、先に書かれていた走査中に戻ってこない問題も解決できそうです。

    サンプルを書いてみました。


    public partial class Form1 : Form
    {
     public Form1()
     {
      InitializeComponent();
      this.label1.Text = "選択パス";
      this.label2.Text = "ファイル数";
     }
     
     private int fileCount;
     private void button1_Click(object sender, EventArgs e)
     {
      if (this.folderBrowserDialog1.ShowDialog() == DialogResult.Cancel) return;

      this.button1.Enabled = false;
      this.listBox1.Items.Clear();

      var root = this.label1.Text = this.folderBrowserDialog1.SelectedPath;
      this.fileCount = 0;
      this.backgroundWorker1.RunWorkerAsync(root);
     }

     // 「フォルダ名,直下のファイル数」な Tuple を列挙するメソッド
     private IEnumerable<Tuple<string, int>> GetFiles(string root)
     {
      return Enumerable.Empty<Tuple<string, int>>()
       .Concat(new[] { new Tuple<string, int>(root, Directory.EnumerateFiles(root).Count()) })
       .Concat(Directory.EnumerateDirectories(root).SelectMany(subDir => GetFiles(subDir)));
     }

     // 進捗報告
     // UserState は「ここまでの総ファイル数,現在のフォルダ名,そのフォルダのファイル数」な Tuple
     private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
     {
      var f = (Tuple<int, string, int>)e.UserState;

      this.fileCount = f.Item1;
      this.label2.Text = this.fileCount.ToString("#,0");
      this.listBox1.Items.Insert(0, string.Format("{0,-10:#,0}\t{1}", f.Item3, f.Item2));
     }

     // 処理結果
     // Argument は「列挙結果のファイル数」
     private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
     {
      this.fileCount = (int)e.Result;
      this.label2.Text = this.fileCount.ToString("#,0");
      this.button1.Enabled = true;
      MessageBox.Show("完了", "列挙テスト", MessageBoxButtons.OK, MessageBoxIcon.Information);
     }

     // ワーカースレッド
     private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
     {
      var bgw = (BackgroundWorker)sender;
      string root = (string)e.Argument;

      int files = 0;
      foreach (var entry in GetFiles(root))
      {
       files += entry.Item2;
       if (bgw.WorkerReportsProgress)
       {
        bgw.ReportProgress(0, new Tuple<int, string, int>(files, entry.Item1, entry.Item2));
       }
      }
      e.Result = files;
     }
    }


    例外対策を入れてないので、アクセス権のない場所、たとえば
     C:\Users\<userName>\AppData\Local\Application Data
     C:\$RECYCLE.BIN\S-1-5-21-……
     C:\Program Files (x86)\Google\CrashReports
    などに到達すると列挙が失敗します。

    その場合の対策などは、下記を参照してみて下さい。
    http://stackoverflow.com/questions/5098011/directory-enumeratefiles-unauthorizedaccessexception
引用返信 削除キー/
■32921 / inTopicNo.8)  Re[4]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(4回)-(2015/04/28(Tue) 16:51:45)
  • アイコンNo32919に返信(shuさんの記事)

    > 直下のフォルダを取得して各フォルダに対し今の処理を行うようにしてそのフォルダ配下がすべて完了したらカウント1アップのようなイメージです。

    試させていただきました。
    直下フォルダの取得は早く、最初の待ち時間が短くなり良い感じになりました。

    質問時に私のタイプミスでファイル総数が1万件になっていましたが、
    10万件の間違いでした。

    1フォルダ内のファイル数が多い場合(フォルダよって内容数は変化します)
    カウントアップに時間が掛かる事があるので、「もうちょっと何とかならない?」と言われてしまいまして。
    もう少しイロイロ試行錯誤してみたいと思います。

    プログレスバーを2つ配置してサブフォルダ内の処理時の進捗を出そうかとも思っているのですが、階層が深い時にどうしたものかと。


    > 4000フォルダ情報を読むのに時間がかかるようなら総数に対する進捗はあきらめた方がよいと思います。EnumerateDirectories, EnumerateFilesにより順次処理を行うようにすると一括で情報取得をしない分待ち時間を分散出来ると思います。

    使用者にどの位で終わるかの目安ですので、プログレスバーを使わずに
    ファイル処理のスレッドとは別のスレッドでファイル総数を求め
    「処理中のファイルカウント/ファイル総数」
    のような表示方法も検討しています。


引用返信 削除キー/
■32922 / inTopicNo.9)  Re[4]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(5回)-(2015/04/28(Tue) 17:10:20)
  • アイコンNo32920に返信(魔界の仮面弁士さんの記事)

    > サンプルを書いてみました。

    サンプルありがとうございます。

    前回のお返事いただいてから、再帰を使ってファイル数を求めるものは作ってみたのですが、
    ほとんど時間は変わらずでしたので、他の方法はないか試行錯誤していました。

    処理部とファイル総数を求める部分を別スレッドにしてみたりしたのですが、
    ○○%でフラフラして、思っていた以上に動きが不審な感じになってしまいました。

    Tapleクラスの存在を知らなかったので、そこも勉強しつつになりますが、
    サンプルを参考にさせていただき、試してみたいと思います。


引用返信 削除キー/
■32923 / inTopicNo.10)  Re[5]: フォルダ内のファイル数の取得
□投稿者/ 魔界の仮面弁士 大御所(957回)-(2015/04/28(Tue) 17:51:01)
  • アイコンNo32922に返信(レインさんの記事)
    > ほとんど時間は変わらずでしたので、他の方法はないか試行錯誤していました。

    Process クラス、あるいはファイルへのリダイレクトで
     DIR /A:-D /S /B C:\HOGE\
    のコマンドの標準出力を受け取り、改行の数を数えてみるとか。

    処理時間については計測していないので分かりませんけど。


    > Tapleクラスの存在を知らなかったので、そこも勉強しつつになりますが、
    すみません、手抜きです…。本来は
    Tuple (タプル/チュープル) を使うのではなく、
    自前のクラスを作った方がわかりやすいかと。

引用返信 削除キー/
■32924 / inTopicNo.11)  Re[6]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(6回)-(2015/04/28(Tue) 18:18:52)
  • アイコンNo32923に返信(魔界の仮面弁士さんの記事)

    > Process クラス、あるいはファイルへのリダイレクトで
    >  DIR /A:-D /S /B C:\HOGE\
    > のコマンドの標準出力を受け取り、改行の数を数えてみるとか。

    dir案は私も考えていました。
    コードで実行したものではなくコマンドプロンプト上で実行し、
    ストップウォッチ片手にというアナログな方法で実測してみたところ、
    最初の
    Directory.GetFiles(FolderName, "*", SearchOption.AllDirectories).Length;
    と大差ないくらいの結果でした。


    > Tuple (タプル/チュープル) を使うのではなく、
    > 自前のクラスを作った方がわかりやすいかと。

    Tupleはまだどういう用途で使うのかも理解できなかったりしてますが、
    後学のためにも使い方くらいは覚えたいと思います。

引用返信 削除キー/
■32925 / inTopicNo.12)  Re[7]: フォルダ内のファイル数の取得
□投稿者/ 魔界の仮面弁士 一般人(1回)-(2015/04/28(Tue) 18:24:40)
  • アイコン2015/04/28(Tue) 18:25:07 編集(投稿者)

    No32924に返信(レインさんの記事)
    > dir案は私も考えていました。

    あとは、FINDEX_INFO_LEVELS.FindExInfoBasic 指定の FindFirstFileEx とか。
    http://kkamegawa.hatenablog.jp/entry/20100918/p1
引用返信 削除キー/
■32931 / inTopicNo.13)  Re[8]: フォルダ内のファイル数の取得
□投稿者/ レイン 一般人(7回)-(2015/04/30(Thu) 11:24:46)
  • アイコン
    No32925に返信(魔界の仮面弁士さんの記事)
    > あとは、FINDEX_INFO_LEVELS.FindExInfoBasic 指定の FindFirstFileEx とか。
    
    APIのFindFirstFileExでテストしてみたのですが、大体35秒くらいでした。
    
    
    
    public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;    //ディレクトリ
    
    public enum FINDEX_INFO_LEVELS
    {
        FindExInfoStandard = 0,
        FindExInfoBasic = 1
    }
    
    public enum FINDEX_SEARCH_OPS
    {
        FindExSearchNameMatch = 0,
        FindExSearchLimitToDirectories = 1,
        FindExSearchLimitToDevices = 2
    }
    
    public struct WIN32_FIND_DATA
    {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }
    
    [DllImport("kernel32.dll")]
    public static extern IntPtr FindFirstFileEx(
        string lpFileName,
        FINDEX_INFO_LEVELS fInfoLevelId,
        out WIN32_FIND_DATA lpFindFileData,
        FINDEX_SEARCH_OPS fSearchOp,
        IntPtr lpSearchFilter,
        int dwAdditionalFlags);
    
    [DllImport("kernel32.dll")]
    public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
    
    [DllImport("kernel32.dll")]
    public static extern bool FindClose(IntPtr hFindFile);
    
    private void FindFile(string lpFilename)
    {
        WIN32_FIND_DATA findData;
    
        IntPtr hFile = FindFirstFileEx(
            lpFilename,
            FINDEX_INFO_LEVELS.FindExInfoBasic,
            out findData,
            FINDEX_SEARCH_OPS.FindExSearchNameMatch,
            IntPtr.Zero,
            0);
    
        if(hFile.ToInt32() != -1)
        {
            do
            {
                if((findData.dwFileAttributes) != FILE_ATTRIBUTE_DIRECTORY)
                    ++FileCount;
                else
                {
                    if(findData.cFileName == "." || findData.cFileName == "..")
                        continue;
                    else
                    {
                        FindFile(Path.Combine(Path.GetDirectoryName(lpFilename), findData.cFileName, @"*"));
                    }
                }
            } while(FindNextFile(hFile, out findData));
    
            FindClose(hFile);
        }
    }
    
    private int FileCount = 0;
    private string SrcFolderName = @"c:\hoge\";
    
    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        FileCount = 0;
        FindFile(Path.Combine(SrcFolderName, @"*"));
    }
    
    
    
    ファイル総数を求めて進捗状況を表示するというのは、なかなか難しそうですね。
    別のアプローチを考えてみたいと思います。
    

引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

Mode/  Pass/


- Child Tree -