枚举文件的目录(多个扩展名)&不使用MFT

时间:2015-09-13 22:58:38

标签: c# wpf

我的目标是枚举具有多个扩展名的文件的目录。例如,我将在整个c:\驱动器中搜索扩展名为.mp3,mp2和mp1的文件。我知道在文件系统这样的NTFS中执行此操作的最快方法是使用MFT。

我的第一个问题是;

我在不使用MFT的情况下编写了一个函数。

public static List<string> GetDirectoryFiles(string rootPath, List<String> extensions, SearchOption searchOption)
        {
            List<string> foundFiles = new List<string>(); // Start with an empty container

            if (searchOption == SearchOption.AllDirectories)
            {
                try
                {
                    IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
                    foreach (string dir in subDirs)
                    {
                        foreach (string file in GetDirectoryFiles(dir, extensions, searchOption))
                            foundFiles.Add(file);

                    }
                }
                catch (UnauthorizedAccessException) { } // Incase we have an access error - we don't want to mask the rest
            }

            try
            {

                foreach (string file in Directory.EnumerateFiles(rootPath, "*.*"))
                {
                    bool correctExt = false;

                    foreach (string ext in extensions)
                    {

                        if (file.EndsWith(ext))
                            correctExt = true;
                    }

                    if(correctExt)
                    {
                        foundFiles.Add(file);
                    }

                }

            }
            catch (UnauthorizedAccessException) { } // Incase we have an access error - we don't want to mask the rest

            return foundFiles; // This is it finally
        }

此功能有效但即使在SSD上也很慢。有没有更快的方法来做到这一点?或者我可以改进此功能以使其更快?我将在FAT32等文件系统上使用它。

我的第二个问题是如何转换此功能以使其使用MFT。我知道在VB.NET中有一个名为&#34; MFT SCANNER的项目&#34;(它实际上在c#中)https://mftscanner.codeplex.com/。但是我无法知道如何将它用于我的目标。

1 个答案:

答案 0 :(得分:1)

完成相当数量的这一点,虽然当你使用特定于FS的方法(如MFT枚举)时可以获得一些好的速度提升,但这需要大量的工作并且通常需要提升的权限来运行。作为一般规则,我尽量避免这种情况。这也意味着您必须进行各种测试以确定您的许多枚举器中的哪一个将在特定实例中为您工作。最好让操作系统处理所有这些...这就是为什么我们有抽象层:))

我发现最快的通用方法是直接调用Windows FindFirstFile / FindNextFile API。这适用于Windows支持的所有文件系统,包括网络连接的文件系统。

这里有一些旧项目的代码:

public sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [DllImport("kernel32.dll")]
    private static extern bool FindClose(IntPtr handle);

    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    internal SafeFindHandle()
        : base(true)
    { }

    protected override bool ReleaseHandle()
    {
        if (IsInvalid)
            return true;
        return FindClose(base.handle);
    }
}

public static class FileEnumerator
{
    [Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto), BestFitMapping(false)]
    internal class WIN32_FIND_DATA
    {
        public FileAttributes dwFileAttributes;
        public uint ftCreationTime_dwLowDateTime;
        public uint ftCreationTime_dwHighDateTime;
        public uint ftLastAccessTime_dwLowDateTime;
        public uint ftLastAccessTime_dwHighDateTime;
        public uint ftLastWriteTime_dwLowDateTime;
        public uint ftLastWriteTime_dwHighDateTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public int dwReserved0;
        public int dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern SafeFindHandle FindFirstFile(string fileName,
        [In, Out] WIN32_FIND_DATA data);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool FindNextFile(SafeFindHandle hndFindFile,
        [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_DATA lpFindFileData);

    public static IEnumerable<string> EnumerateFiles(string path)
    {
        WIN32_FIND_DATA finddata = new WIN32_FIND_DATA();
        Queue<string> paths = new Queue<string>();
        paths.Enqueue(path);

        while (paths.Count > 0)
        {
            var nxtpath = paths.Dequeue();

            using (var fh = FindFirstFile(Path.Combine(nxtpath, "*"), finddata))
            {
                if (fh.IsInvalid)
                    continue;
                bool ok = true;
                while (ok)
                {
                    if ((finddata.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                    {
                        if (finddata.cFileName != "." && finddata.cFileName != "..")
                            paths.Enqueue(Path.Combine(nxtpath, finddata.cFileName));
                    }
                    else
                        yield return Path.Combine(nxtpath, finddata.cFileName);

                    ok = FindNextFile(fh, finddata);
                }
            }
        }
    }
}

这将为您提供可以使用标准LINQ构造过滤的可枚举文件名:

var filenames = FileEnumerator.EnumerateFiles(@"C:\Some\Folder")
    .Where(fn => string.Compare(Path.GetExtension(fn), ".txt", true) == 0);

当然,如果您想要返回的不仅仅是文件名,您只需要确定WIN32_FIND_DATA类中哪些信息是有意义的,然后返回这些信息。如果您可以一次性完成所有操作,而不必为每个文件创建FileInfo实例,则可以节省大量时间。