您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 1浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

使用WinAPI替代System.IO.Directory

寒冰屋 发布时间:2019-04-13 16:52:01 ,浏览量:1

目录

介绍

使用代码

代码如何运作

  • 下载源代码 - 22.6 KB
介绍

最近,我正在做一个需要读取Windows目录内容的项目,所以我使用了.NET提供的System.IO.Directory类中的EnumerateDirectories,EnumerateFiles和EnumerateFileSystemEntries方法。不幸的是,使用这些函数有一个很大的缺点,那就是如果它们遇到拒绝访问当前用户的文件系统条目时,它们会立即中断——而不是处理这样的错误并继续,它们只会返回到中断时收集到的任何内容——并且不会完成工作。

从方法的外部处理它是不可能的,因为如果你处理它,你将只获得返回的IEnumerable部分结果。

我到处寻找解决这个问题的方法,但我找不到不使用上述方法的解决方法。所以我决定使用Windows API并创建替代方法。结果不仅更好(在某种程度上方法不会被“拒绝访问” 破坏),但它似乎比.NET的原始方法更快。

使用代码

项目本身是类库类型,它不是可执行的,但构建它会将方法编译成DLL文件,您可以将其引用到另一个项目中,并从那里使用它,如下所示:

using System.IO;

DirectoryAlternative.EnumerateDirectories
(path, "*", SearchOption.AllDirectories).ToList();

我使用与原始过程(System.IO)相同的命名空间,并命名该类为DirectoryAlternative——因此使用与原始类尽可能相似。

方法本身以相同的方式命名,它们使用相同的参数,并且从外部看起来与原来的完全相同。

以下是方法用法的示例:

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
string path = "V:\\MUSIC";
List en = new List();
sw.Start();
try { en = Directory.EnumerateDirectories
  (path, "*", SearchOption.AllDirectories).ToList(); } catch { }
sw.Stop();
Console.WriteLine("Directory.EnumerateDirectories : {0} ms / {1} entries", 
  sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));
sw.Reset();
en = new List();
sw.Start();
en = DirectoryAlternative.EnumerateDirectories(path, "*", 
  SearchOption.AllDirectories).ToList();
sw.Stop();
Console.WriteLine("DirectoryAlternative.EnumerateDirectories : 
  {0} ms / {1} entries", sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));
sw.Reset();
en = new List();
sw.Start();
try { en = Directory.EnumerateFiles(path, "*", 
  SearchOption.AllDirectories).ToList(); } catch { }
sw.Stop();
Console.WriteLine("Directory.EnumerateFiles : {0} ms / {1} entries", 
  sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));
sw.Reset();
en = new List();
sw.Start();
en = DirectoryAlternative.EnumerateFiles
  (path, "*", SearchOption.AllDirectories).ToList();
sw.Stop();
Console.WriteLine("DirectoryAlternative.EnumerateFiles : {0} ms / {1} entries", 
  sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));
sw.Reset();
en = new List();
sw.Start();
try { en = Directory.EnumerateFileSystemEntries
  (path, "*", SearchOption.AllDirectories).ToList(); } catch { }
sw.Stop();
Console.WriteLine("Directory.EnumerateFileSystemEntries : {0} ms / {1} entries", 
  sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));
sw.Reset();
en = new List();
sw.Start();
en = DirectoryAlternative.EnumerateFileSystemEntries
  (path, "*", SearchOption.AllDirectories).ToList();
sw.Stop();
Console.WriteLine("DirectoryAlternative.EnumerateFileSystemEntries : {0} ms / {1} entries", 
  sw.ElapsedMilliseconds.ToString("N0"), en.Count.ToString("N0"));

Console.ReadKey();

上面的代码片段直接比较了原始方法的性能和DirectoryAlternative方法——我使用了一个包含70.000+文件系统条目的非常大的目录:

如您所见,DirectoryAlternative方法的运行速度快了近2倍。

代码如何运作

代码使用几个Win API函数来移动文件系统(我相信在原始的.NET方法中使用了这些相同的函数):

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
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", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool FindClose(IntPtr hFindFile);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr FindFirstFile
  (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool FindNextFile
  (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

简而言之:

  • FindFirstFile搜索它可以使用提供的pattern(lpFileName)找到的第一个文件系统条目,并将HANDLE(IntPtr)返回到此文件
  • FindNextFile 搜索与指定模式匹配的下一个文件系统条目——我们使用此方法遍历所有文件/目录
  • FindClose 用于关闭 HANDLE

所有文件信息都收集在WIN32_FIND_DATA struct内部并作为out类型参数返回。

有关这些方法的更多信息,您可以在此处查找。

主要方法是Enumerate方法。所有其他方法都围绕着这个方法。

private static void Enumerate(string path, string searchPattern, 
  SearchOption searchOption, ref List retValue, EntryType entryType)
{
    WIN32_FIND_DATA findData;
    if (path.Last() != '\\') path += "\\";
    IntPtr hFile = FindFirstFile(path + searchPattern, out findData);
    List subDirs = new List();

    if (hFile.ToInt32() != -1)
    {
        do
        {
            if (findData.cFileName == "." || findData.cFileName == "..") continue;
            if ((findData.dwFileAttributes & 
               (uint)FileAttributes.Directory) == (uint)FileAttributes.Directory)
            {
                subDirs.Add(path + findData.cFileName);
                if (entryType == EntryType.Directories || 
                    entryType == EntryType.All) retValue.Add(path + findData.cFileName);
            }
            else
            {
                if (entryType == EntryType.Files || 
                    entryType == EntryType.All) retValue.Add(path + findData.cFileName);
            }
        } while (FindNextFile(hFile, out findData));
        if (searchOption == SearchOption.AllDirectories)
            foreach (string subdir in subDirs)
                Enumerate(subdir, searchPattern, searchOption, ref retValue, entryType);
    }
    FindClose(hFile);
}

该方法从原始Enumerate的方法(path,searchPattern,searchOption)中获取所有参数,再加上引用的参数retValue和entryType,这是一个enum:

private enum EntryType { All = 0, Directories = 1, Files = 2 };

这enum用作选择器,是否只返回目录,仅文件或两者。

该Enumerate方法通过调用调用FindFirstFile并随后通过FindNextFile遍历所有其他文件系统条目。如果entryType = Files,它将添加retValue列表中的所有文件。对于Directories,它只会添加目录,对于All它会添加两者。

如果searchOption = AllDirectories,则递归调用该方法。所有(递归)调用的结果都收集在一个变量retValue中。在方法的第一个版本中,我使用了返回类型List,并在每次递归调用后将其连接到调用函数的retValue变量中,但是使用by-ref参数的解决方案被证明要快得多。

最后,通过调用FindClose方法将关闭每个调用HANDLE 的文件搜索。

 

原文地址:https://www.codeproject.com/Articles/1383832/System-IO-Directory-Alternative-using-WinAPI

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0440s