none
如何高速枚举大量文件名,又能尽量减少内存占用? RRS feed

  • 问题

  • 目前有一个难题:A文件夹里面有xml和tif两种文件(合计总共有:500万个)。

    现在要进行配对工作,就是忽略文件后缀,假如有一对xml和tif文件名相同,就忽略不管;假如有找不到配对的xml或tif文件,就把它们移动到B文件夹。

    我使用了HashSet加载所有文件名,进行迭代处理,但是这样做的话,内存占用很大,有将近500M,CPU占用在40%-50%浮动。

    核心代码如下,请问该如何改进,能优化程序的资源占用。我已经出尽全力了T_T(代码比较长,请见谅):

    HashSet<string> xmlFileList = new HashSet<string>(); HashSet<string> tifFileList = new HashSet<string>(); HashSet<string> callList1 = new HashSet<string>(Directory.EnumerateFiles(sourcePath, "*.XML")); foreach (string fileName in callList1) { string name = Path.GetFileNameWithoutExtension(fileName); xmlFileList.Add(name); } HashSet<string> callList2 = new HashSet<string>(Directory.EnumerateFiles(sourcePath, "*.tif")); foreach (string fileName in callList2) { string name = Path.GetFileNameWithoutExtension(fileName); tifFileList.Add(name); } foreach (string fileName in xmlFileList) { if (tifFileList.Contains(fileName)) { equalList.Add(fileName); } } foreach (string fileName in equalList) { xmlFileList.Remove(fileName); tifFileList.Remove(fileName); } foreach (string fileName in xmlFileList) { string targetFileName1 = fileName + ".XML"; File.Move(sourcePath + targetFileName1, targetPath + targetFileName1); } foreach (string fileName in tifFileList) { string targetFileName2 = fileName + ".tif"; File.Move(sourcePath + targetFileName2, targetPath + targetFileName2); }

    2012年12月21日 15:45

答案

  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    你好,我不清楚这么做目的何在?

    因为你的磁盘文件数目巨大,用常规方式可能无法解决问题,尽量能使用系统功能。

    首先要解决内存占用问题,那么你就不能一次性的先读入所有的文件,而是一边读入,一边比较。如果文件是乱序的,那么你就需要一边比较,一边从HashSet中删除已经配对的文件名,最坏的情况就是你读入了所有的XML文件(Mike Feng 通过 File.Exists解决了此问题,但是大量的 File.Exists 调用会影响性能)

    如果先排了序,那么就可以简单的使用单循环和字符串比较来比较 2n 和 2n+1 这两个文件是否是同一个文件。

    我没做过实际测试,你要愿意可以自己尝试下,两种方式的性能。

    File.Move(fileName, targetPath + targetFileName1); 的调用可以用另一个线程来处理,同时,那么也可以用ReadFile WriteFile的异步版本来来替代MoveFile。

    当然,我还是建议把没有配对的文件先保存起来,然后一次性的将它们 Move 走。


    我之前说得不详细。影响性能的地方,主要指的是HashSet加载了大量数据,所以重点请教大家关于思路的优化。File.Move暂时可以不考虑它。

    如果只是这样的话,Mike Feng 的代码修改下就可以:

    var txtFiles = Directory.EnumerateFiles(sourceDirectory);

    foreach (string currentFile in txtFiles)
    {
         string name = Path.GetFileNameWithoutExtension(fileName);
                  
    if( false == File.Exists (name + ".xml") )
                      
    File.Move(fileName, targetPath + targetFileName1); continue;
                  
    if (false == File.Exists(name + ".tif"))
                      
    File.Move(fileName, targetPath + targetFileName1);
    }
    2012年12月26日 8:02
  • DroidXgnaW 你好,谢谢你在MSDN 论坛上帮助了如此多的人。 谢谢你。当然还有编程志愿者。

    @DroidXgnaW, 你上面的修改应该没有多少变化。

    @moon_prince,考虑的managed 方法需要将所有的文件名读到内存中然后才能遍历,这应该是消耗内存的大头。所以除了上面我提到的Index search,不妨试试Windows APIs.

    1. 首先Call FindFirstFile 找到该文件夹下的第一个文件,其返回值是一个search handle。第一个参数给一个包含通配符的字符串,如“d:\abc\*.*”,调用结束后,将第二个参数里的文件名去掉扩展名放到一个list<String> 里面。

    2.循环调用 FindNextFile,循环条件是FindNextFile 返回true。 然后将FindNextFile的第二个参数里的文件名去掉扩展名后调用 第一步中的List<String>.Exist 方法,若返回true表示已经找到匹配的,那么将该文件名从list<string> 里删掉,若没有,则添加进去。

    3. 到循环结束后,调用FindClose 关闭search handle。

    4. list<string>里放都是符合你需求的文件名,但是没有后缀,这个不要紧,可以将上面managed code稍微改下,就可以开始移文件了:

                   if( true == File.Exists (name + ".xml") )
                       File.Move(fileName, targetPath + targetFileName1); continue;
                   if (true == File.Exists(name + ".tif"))
                       File.Move(fileName, targetPath + targetFileName1); 

    这样可以解决内存占用率高的问题,但是频繁调用API,应该比较慢,不过也不一定,我这里没有500W的文件,所以也没测试。麻烦你测试下,然后告诉我结果。谢谢!


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    2012年12月27日 5:11
    版主

全部回复

  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    2012年12月24日 1:53
  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    你好,我不清楚这么做目的何在?
    2012年12月24日 4:48
  • 你好,

    你可以试试Windows Desktop Search: http://msdn.microsoft.com/en-us/library/aa965362%28VS.85%29.aspx 

    http://msdn.microsoft.com/ZH-CN/library/windows/desktop/bb266517(v=vs.85).aspx

    另外,你 上面的程序用了 6个ForEach,实际上你只需要一个遍历就够了。

                HashSet<string> callList1 = new HashSet<string>(Directory.EnumerateFiles(sourcePath, "*.*"));
                foreach (string fileName in callList1)
                {
                    string name = Path.GetFileNameWithoutExtension(fileName);
                   if( false == File.Exists (name + ".xml") )
                       File.Move(fileName, targetPath + targetFileName1); continue;
                   if (false == File.Exists(name + ".tif"))
                       File.Move(fileName, targetPath + targetFileName1); 
                }

    我这一个foreach相当于你的第一个和第二个foreach,但省去了其余四个,你可以试试。主要还是看看最开始说的window Search。不过关于windows search的问题,到这里问:http://social.msdn.microsoft.com/Forums/en-US/windowsdesktopsearchdevelopment/threads

    谢谢!


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2012年12月24日 7:42
    版主
  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    你好,我不清楚这么做目的何在?

    因为你的磁盘文件数目巨大,用常规方式可能无法解决问题,尽量能使用系统功能。

    首先要解决内存占用问题,那么你就不能一次性的先读入所有的文件,而是一边读入,一边比较。如果文件是乱序的,那么你就需要一边比较,一边从HashSet中删除已经配对的文件名,最坏的情况就是你读入了所有的XML文件(Mike Feng 通过 File.Exists解决了此问题,但是大量的 File.Exists 调用会影响性能)

    如果先排了序,那么就可以简单的使用单循环和字符串比较来比较 2n 和 2n+1 这两个文件是否是同一个文件。

    我没做过实际测试,你要愿意可以自己尝试下,两种方式的性能。

    File.Move(fileName, targetPath + targetFileName1); 的调用可以用另一个线程来处理,同时,那么也可以用ReadFile WriteFile的异步版本来来替代MoveFile。

    当然,我还是建议把没有配对的文件先保存起来,然后一次性的将它们 Move 走。


    • 已编辑 Skyseer 2012年12月24日 8:20
    2012年12月24日 8:08
  • 大家的想法都挺不错的,等明天再试试,最近事情比较多,呵呵。
    2012年12月25日 7:24
  • 使用线程来完成此任务,或者使用BackGroundWorker类看看。

    参考这个例子:

    http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker%28v=VS.80%29.aspx


    帮助一起改进论坛质量?提交你的意见于此。
    我的博客园
    慈善点击,点击此处
    和谐拯救危机,全集下载,净化人心

    使用线程了:

    将第一个foreach和第二个foreach拆成两个线程运行,会导致两个线程之间任意一个运行失败(会运行一段时间,但结果是失败的)。

    结果占用更BT,主要是CPU从原来50%的占用,飙升到了80%。所以这个方法不适合。

    2012年12月26日 7:25
  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    你好,我不清楚这么做目的何在?

    因为你的磁盘文件数目巨大,用常规方式可能无法解决问题,尽量能使用系统功能。

    首先要解决内存占用问题,那么你就不能一次性的先读入所有的文件,而是一边读入,一边比较。如果文件是乱序的,那么你就需要一边比较,一边从HashSet中删除已经配对的文件名,最坏的情况就是你读入了所有的XML文件(Mike Feng 通过 File.Exists解决了此问题,但是大量的 File.Exists 调用会影响性能)

    如果先排了序,那么就可以简单的使用单循环和字符串比较来比较 2n 和 2n+1 这两个文件是否是同一个文件。

    我没做过实际测试,你要愿意可以自己尝试下,两种方式的性能。

    File.Move(fileName, targetPath + targetFileName1); 的调用可以用另一个线程来处理,同时,那么也可以用ReadFile WriteFile的异步版本来来替代MoveFile。

    当然,我还是建议把没有配对的文件先保存起来,然后一次性的将它们 Move 走。


    我之前说得不详细。影响性能的地方,主要指的是HashSet加载了大量数据,所以重点请教大家关于思路的优化。File.Move暂时可以不考虑它。
    2012年12月26日 7:50
  • 你可以先测试下在资源管理器中对文件按名称排序需要多长时间,看看有没有cmd命令可以实现此功能。

    你好,我不清楚这么做目的何在?

    因为你的磁盘文件数目巨大,用常规方式可能无法解决问题,尽量能使用系统功能。

    首先要解决内存占用问题,那么你就不能一次性的先读入所有的文件,而是一边读入,一边比较。如果文件是乱序的,那么你就需要一边比较,一边从HashSet中删除已经配对的文件名,最坏的情况就是你读入了所有的XML文件(Mike Feng 通过 File.Exists解决了此问题,但是大量的 File.Exists 调用会影响性能)

    如果先排了序,那么就可以简单的使用单循环和字符串比较来比较 2n 和 2n+1 这两个文件是否是同一个文件。

    我没做过实际测试,你要愿意可以自己尝试下,两种方式的性能。

    File.Move(fileName, targetPath + targetFileName1); 的调用可以用另一个线程来处理,同时,那么也可以用ReadFile WriteFile的异步版本来来替代MoveFile。

    当然,我还是建议把没有配对的文件先保存起来,然后一次性的将它们 Move 走。


    我之前说得不详细。影响性能的地方,主要指的是HashSet加载了大量数据,所以重点请教大家关于思路的优化。File.Move暂时可以不考虑它。

    如果只是这样的话,Mike Feng 的代码修改下就可以:

    var txtFiles = Directory.EnumerateFiles(sourceDirectory);

    foreach (string currentFile in txtFiles)
    {
         string name = Path.GetFileNameWithoutExtension(fileName);
                  
    if( false == File.Exists (name + ".xml") )
                      
    File.Move(fileName, targetPath + targetFileName1); continue;
                  
    if (false == File.Exists(name + ".tif"))
                      
    File.Move(fileName, targetPath + targetFileName1);
    }
    2012年12月26日 8:02
  • DroidXgnaW 你好,谢谢你在MSDN 论坛上帮助了如此多的人。 谢谢你。当然还有编程志愿者。

    @DroidXgnaW, 你上面的修改应该没有多少变化。

    @moon_prince,考虑的managed 方法需要将所有的文件名读到内存中然后才能遍历,这应该是消耗内存的大头。所以除了上面我提到的Index search,不妨试试Windows APIs.

    1. 首先Call FindFirstFile 找到该文件夹下的第一个文件,其返回值是一个search handle。第一个参数给一个包含通配符的字符串,如“d:\abc\*.*”,调用结束后,将第二个参数里的文件名去掉扩展名放到一个list<String> 里面。

    2.循环调用 FindNextFile,循环条件是FindNextFile 返回true。 然后将FindNextFile的第二个参数里的文件名去掉扩展名后调用 第一步中的List<String>.Exist 方法,若返回true表示已经找到匹配的,那么将该文件名从list<string> 里删掉,若没有,则添加进去。

    3. 到循环结束后,调用FindClose 关闭search handle。

    4. list<string>里放都是符合你需求的文件名,但是没有后缀,这个不要紧,可以将上面managed code稍微改下,就可以开始移文件了:

                   if( true == File.Exists (name + ".xml") )
                       File.Move(fileName, targetPath + targetFileName1); continue;
                   if (true == File.Exists(name + ".tif"))
                       File.Move(fileName, targetPath + targetFileName1); 

    这样可以解决内存占用率高的问题,但是频繁调用API,应该比较慢,不过也不一定,我这里没有500W的文件,所以也没测试。麻烦你测试下,然后告诉我结果。谢谢!


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    2012年12月27日 5:11
    版主
  • 是没多少变化,只是把 :

    HashSet<string> callList1 = new HashSet<string>(Directory.EnumerateFiles(sourcePath, "*.*"));

    改为:

    var callList1 = Directory.EnumerateFiles(sourcePath, "*.*");

    这样就不会先枚举完全部读到 HastSet 中。

    MSDN 上说:

    EnumerateFiles GetFiles 方法不同点在于:当使用 EnumerateFiles 时,您可以在返回整个集合之前开始枚举名称集合;当您使用 GetFiles 时,则必须等待整个名称数组都被返回后才能访问数组。</sentencetext>EnumerateFiles 可能更高效。</sentencetext>

    <span data-guid="ad9b1865a7b011edb27806c33f1d12e8" data-source="Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient." id="mt15" xml:space="preserve"><sentencetext xmlns="http://www.w3.org/1999/xhtml">因此我猜测它的内部应该就是用的 FindNextFile 来实现的。</sentencetext>

    2012年12月27日 5:24
  • >>因此我猜测它的内部应该就是用的 FindNextFile 来实现的。

    的确如此。


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2012年12月27日 5:59
    版主
  • 不好意思大家,最近挺忙的,都没什么时间来按帖子写程序做测试了。
    2013年1月4日 1:04