获取多个FileInfo的更快方法?

17

这可能是一个不太可能的任务,但有没有更快速的方法可以获取多个文件的大小、上次访问时间、上次创建时间等信息?

我有一个长列表的文件路径(因此无需枚举),需要尽快查找这些信息。并行创建FileInfo可能帮助不大,因为瓶颈应该在磁盘上。

遗憾的是,NTFS日志只保留了文件名,否则那将是很好的,我想操作系统没有在某个地方存储那些元数据信息吧?

如果有一个静态或Win32调用(File方法只允许我一次获取一个信息)的方法可以获取这些信息,那么可能会进行另一种优化,而不是创建一堆FileInfo对象。

总之,如果有人知道任何可能有所帮助的内容,那就很高兴,不幸的是,我必须在这里进行微小的优化,"使用数据库"并不是可行的答案 ;)


请查看通过NuGet提供的FluentPath。http://weblogs.asp.net/bleroy/archive/2010/11/19/fluentpath-1-0.aspx - jvanrhyn
据我理解,它是一个更好的用于处理文件路径和Linq风格操作的库,在底层并不涉及太多文件元数据。 - Homde
如果没有,你可以在应用程序的生命周期开始时缓存信息吗?这将使信息在RAM中可用,但如果您有长时间运行的应用程序,则不会是最新的。 - Patrick
请查看 https://sourceforge.net/p/fastfileinfo/。 - Loathing
5个回答

11

System.IO.File上有静态方法可以获取你想要的东西。这是微小的优化,但可能就是你需要的:GetLastAccessTimeGetCreationTime

编辑

我会保留上面的文本,因为你特别要求使用静态方法。然而,我认为最好使用FileInfo(你应该测量一下以确保)。File和FileInfo都使用名为FillAttributeInfo的内部方法来获取所需的数据。对于你需要的属性,FileInfo将需要调用此方法一次。每次调用File都必须调用它,因为属性信息对象在方法完成时被丢弃(因为它是静态的)。

所以我的直觉是,在需要多个属性时,对于每个文件,使用FileInfo更快。但在性能情况下,您应该总是进行测量!面对这个问题,我会尝试上述两种托管选项,并进行基准测试,分别在串行和并行运行时。然后决定是否足够快。

如果速度还不够快,您需要直接调用Win32 API。查看参考源中的File.FileAttributeInfo并提出类似的代码不会太难。

第二次编辑

事实上,如果你真的需要它,这是使用与File内部代码相同的方法直接调用Win32 API所需的代码,但只使用一个OS调用来获取所有属性。你将必须手动从FILETIME解析为可用的日期时间等,因此还需进行一些额外的工作。

static class FastFile
{
    private const int MAX_PATH = 260;
    private const int MAX_ALTERNATE = 14;

    public static WIN32_FIND_DATA GetFileData(string fileName)
    {
        WIN32_FIND_DATA data;
        IntPtr handle = FindFirstFile(fileName, out data);
        if (handle == IntPtr.Zero)
            throw new IOException("FindFirstFile failed");
        FindClose(handle);
        return data;
    }

    [DllImport("kernel32")]
    private static extern IntPtr FindFirstFile(string fileName, out WIN32_FIND_DATA data);

    [DllImport("kernel32")]
    private static extern bool FindClose(IntPtr hFindFile);


    [StructLayout(LayoutKind.Sequential)]
    public struct FILETIME
    {
        public uint dwLowDateTime;
        public uint dwHighDateTime;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WIN32_FIND_DATA
    {
        public FileAttributes dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public int nFileSizeHigh;
        public int nFileSizeLow;
        public int dwReserved0;
        public int dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
        public string cAlternate;
    }
}

是的,但接着我必须进行4个单独的调用,如果每个调用都必须进行磁盘IO,那就不太好了。虽然我只是在推测,但FileInfo可能会这样做...我试着查看源代码。 - Homde
@MattiasK,你在假设需要磁盘IO。操作系统可能已经将大量元数据缓存在内存中,这使得它能够响应您的请求而不必访问磁盘。您已经声明了您必须进行微观优化,您是否通过实际确定未经优化需要多长时间来验证了这一点?=) - Rob
实际上,我在ClearCase动态视图(网络驱动器)上使用GetLastWriteTime时速度非常慢。看起来GetLastWriteTime调用Win32 CreateFile来检索句柄以获取文件信息,这也导致病毒扫描程序启动。然而,通过使用FindFirstFile,ClearCase仅返回缓存的文件信息,这快了10倍。感谢可复制的代码! - schletti2000
顺便提一下,使用FIND_FIRST_EX_LARGE_FETCHFindFirstFileEx在通过UNC查找1000多个文件时比Directory.EnumerateFiles + GetLastWriteTimeUtc + GetShortPathName快628.54倍。EnumerateFiles: 4,085.535毫秒,误差33.3309毫秒,标准偏差31.1777毫秒 FindFirstFileEx: 13.761毫秒,误差0.1786毫秒,标准偏差0.1670毫秒 FindFirstFileEx (FIND_FIRST_EX_LARGE_FETCH): 6.500毫秒,误差0.1274毫秒,标准偏差0.1467毫秒 - jnm2

5

.NET的DirectoryInfo和FileInfo类在这方面非常缓慢,特别是在与网络共享使用时。

如果要“扫描”的许多文件位于同一目录中,则通过使用Win32 API的FindFirstFile、FindNextFile和FindClose函数可以获得更快的结果(根据情况而定:速度更快)。即使您必须请求比实际需要更多的信息(例如,如果您要求一个目录中的所有“.log”文件,其中您只需要其中的75%)。

实际上,.NET的信息类也在内部使用这些Win32 API函数。但是它们只“记住”文件名。当请求一堆文件的更多信息(例如LastModified)时,会为每个文件单独(通过网络)发出请求,这需要时间。


有趣,不知道是否有对使用findfirstfile/findnextfile在本地文件目录中读取顺序文件进行优化的方法。也很有趣知道操作系统是否会缓存这样的元数据。 - Homde
7
使用DirectoryInfo.EnumerateFiles/Directories()方法,在4.0版本中已经修复。 - Hans Passant

3

是否可以使用DirectoryInfo类?

 DirectoryInfo d = new DirectoryInfo(@"c:\\Temp");
 FileInfo[] f= d.GetFiles()

我已经有了文件路径,再次枚举它们似乎是浪费时间的,而且我怀疑这种方法是否比简单创建fileinfo更快。 - Homde
我认为,你需要使用本地的Win API调用,或者更好的方法是在将来的.NET版本中请求此功能。 - TalentTuner
谢谢这个。看起来比为每个文件分别执行要快。 - jreichert

1

如果文件系统是远程的,那么并行处理可能会有所帮助,因为网络可能是瓶颈。

这个测试案例显示,在使用8个线程处理50k个文件时,相比于串行处理,可以获得约5倍(52秒=>11秒)的性能提升。此外,避免调用lock()函数非常关键,因为调用它50k次会产生很大的影响。这些时间是在没有运行调试器的情况下进行的。

这也说明了获取文件长度的工作直到访问FileInfo.Length时才执行。在并行部分之后再次访问Length是瞬间完成的。这可能有点过度依赖具体实现。

// ~4s
//
List<string> files = Directory.EnumerateFileSystemEntries(directory, "*", SearchOption.AllDirectories)
    .ToList();

// ~0s
// 
Dictionary<string, FileInfo> fileMap = files.Select(file => new
{
    file,
    info = new FileInfo(file)
})
.ToDictionary(f => f.file, f => f.info);

// ~10s
//
Int64 totalSize = fileMap.Where(kv => kv.Value != null)
    .AsParallel() // ~50s w/o this 
    .Select(kv =>
    {
        try
        {
            return kv.Value.Length;
        }
        catch (FileNotFoundException)  // a transient file or directory
        {
        }
        catch (UnauthorizedAccessException)
        {
        }
        return 0;
    })
    .Sum();

0

我认为你正在寻找GetFileAttributesEx函数(pinvoke.net链接)。然而,FileInfo类(或者说它的基类)已经在内部使用了这个函数,所以我怀疑你不会看到任何性能提升。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接