目录
介绍
使用代码
代码如何运作
- 下载源代码 - 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