确定两个path引用C#中的同一文件的最佳方法
在即将到来的Java7中,有一个新的API来检查两个文件对象是否是相同的文件引用。
.NET框架中是否提供了类似的API?
我已经通过MSDNsearch,但没有任何启发我。
我希望它很简单,但我不想通过文件名进行比较,这会导致硬/符号链接和不同风格的path问题。 (例如\\?\C:\
, C:\
)。
我要做的只是防止重复文件拖放到我的链接列表。
就我所见(1) (2) (3) (4)而言 ,JDK7的方式是通过调用文件的GetFileInformationByHandle并比较dwVolumeSerialNumber,nFileIndexHigh和nFileIndexLow。
每个MSDN:
您可以比较BY_HANDLE_FILE_INFORMATION结构中返回的VolumeSerialNumber和FileIndex成员,以确定两个path是否映射到相同的目标; 例如,您可以比较两个文件path,并确定它们是否映射到相同的目录。
我不认为这个函数是由.NET包装的,所以你将不得不使用P / Invoke 。
它可能会或可能不适用于networking文件。 根据MSDN:
根据操作系统的底层networking组件和连接到的服务器的types,GetFileInformationByHandle函数可能会失败,返回部分信息或给定文件的完整信息。
一个快速的testing表明,它可以像使用SMB / Samba连接的Linux系统上的符号链接一样工作(相同的值),但是当使用指向相同文件的不同共享访问时,它不能检测到文件是相同的FileIndex是相同的,但VolumeSerialNumber不同)。
编辑 :请注意, @Rasmus Faber在Win32 api中提到了GetFileInformationByHandle函数,这就是你想要的,检查和提高他的答案以获取更多信息。
我想你需要一个操作系统的function给你你想要的信息,否则无论你做什么都会有一些错误的消极的。
例如,这些是否指向相同的文件?
- \服务器\共享\path\ FILENAME.TXT
- \服务器\ d $ \ TEMP \path\ FILENAME.TXT
我会研究一下在你的列表中没有重复的文件是多么的重要,然后尽一切努力。
话虽如此,Path类中有一个方法可以完成一些工作: Path.GetFullPath ,根据现有的结构,它至less会扩展到长名称的path。 之后,你只是比较string。 虽然这样做不是万无一失,在我的例子中不会处理上面的两个环节。
答:没有万无一失的方法可以比较string的基本path,以确定它们是否指向同一个文件。
主要原因是表面上不相关的path可能指向完全相同的文件做文件系统redirect(联结,符号链接等)。 例如
“d:\ temp \ foo.txt”“c:\ othertemp \ foo.txt”
这些path可能指向相同的文件。 这种情况明显地消除了任何string比较函数,作为确定两个path是否指向相同文件的基础。
下一个级别是比较OS文件信息。 打开两个path的文件并比较句柄信息。 在Windows中,这可以通过GetFileInformationByHandle完成。 Lucian Wischik在这方面做了一个很好的post 。
这种方法仍然有问题。 它只适用于执行检查的用户帐户能够打开这两个文件进行阅读。 有许多项目可以防止用户打开一个或两个文件。 包括但不仅限于 …
- 缺乏足够的文件权限
- 缺less对文件path中目录的足够权限
- 文件系统在第一个文件打开和第二个文件打开之间发生变化,如networking断开。
当您开始查看所有这些问题时,您将开始理解Windows为什么不提供确定两条path是否相同的方法。 这不是一个简单/可能的问题来回答。
这是一个使用GetFileInformationByHandle
的IsSameFile
的C#实现:
NativeMethods.cs
public static class NativeMethods { [StructLayout(LayoutKind.Explicit)] public struct BY_HANDLE_FILE_INFORMATION { [FieldOffset(0)] public uint FileAttributes; [FieldOffset(4)] public FILETIME CreationTime; [FieldOffset(12)] public FILETIME LastAccessTime; [FieldOffset(20)] public FILETIME LastWriteTime; [FieldOffset(28)] public uint VolumeSerialNumber; [FieldOffset(32)] public uint FileSizeHigh; [FieldOffset(36)] public uint FileSizeLow; [FieldOffset(40)] public uint NumberOfLinks; [FieldOffset(44)] public uint FileIndexHigh; [FieldOffset(48)] public uint FileIndexLow; } [DllImport("kernel32.dll", SetLastError = true)] public static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern SafeFileHandle CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename, [MarshalAs(UnmanagedType.U4)] FileAccess access, [MarshalAs(UnmanagedType.U4)] FileShare share, IntPtr securityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, IntPtr templateFile); }
PathUtility.cs
public static bool IsSameFile(string path1, string path2) { using (SafeFileHandle sfh1 = NativeMethods.CreateFile(path1, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) { if (sfh1.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); using (SafeFileHandle sfh2 = NativeMethods.CreateFile(path2, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) { if (sfh2.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo1; bool result1 = NativeMethods.GetFileInformationByHandle(sfh1, out fileInfo1); if (!result1) throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path1)); NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo2; bool result2 = NativeMethods.GetFileInformationByHandle(sfh2, out fileInfo2); if (!result2) throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path2)); return fileInfo1.VolumeSerialNumber == fileInfo2.VolumeSerialNumber && fileInfo1.FileIndexHigh == fileInfo2.FileIndexHigh && fileInfo1.FileIndexLow == fileInfo2.FileIndexLow; } } }
首先,我认为这很容易,但这不起作用:
string fileName1 = @"c:\vobp.log"; string fileName2 = @"c:\vobp.log".ToUpper(); FileInfo fileInfo1 = new FileInfo(fileName1); FileInfo fileInfo2 = new FileInfo(fileName2); if (!fileInfo1.Exists || !fileInfo2.Exists) { throw new Exception("one of the files does not exist"); } if (fileInfo1.FullName == fileInfo2.FullName) { MessageBox.Show("equal"); }
也许这个库可以帮助http://www.codeplex.com/FileDirectoryPath 。 我自己没有用过
编辑:看到这个网站上的这个例子:
// // Path comparison // filePathAbsolute1 = new FilePathAbsolute(@"C:/Dir1\\File.txt"); filePathAbsolute2 = new FilePathAbsolute(@"C:\DIR1\FILE.TXT"); Debug.Assert(filePathAbsolute1.Equals(filePathAbsolute2)); Debug.Assert(filePathAbsolute1 == filePathAbsolute2);
如果你需要一遍又一遍地比较相同的文件名,我build议你看看这些名字的无调性。
在Unix系统下,有realpath()函数来调整你的path。 如果你有复杂的path,我认为这通常是最好的select。 但是,通过networking连接安装的卷可能会失败。
但是,基于realpath()方法,如果要支持包括networking卷在内的多个卷,则可以编写自己的函数来检查path中的每个目录名,并且如果它引用卷,则确定两个path中的卷引用是一样的。 这就是说,挂载点可能是不同的(即目的地卷上的path可能不是该卷的根),因此解决所有问题并不容易,但它是完全可能的(否则如何它会工作在第一位?!)
一旦文件名正确无效,一个简单的string比较会给你正确的答案。
Rasmus的答案可能是最快的方法,如果你不需要一遍又一遍比较相同的文件名。
您可以始终在两者上执行MD5编码并比较结果。 不完全有效,但比自己比较文件更容易。
这里是一个关于如何在C#中MD5string的文章 。