2つのパスがC#の同じファイルを参照しているかどうかを判断する最良の方法
-
03-07-2019 - |
質問
今後のJava7には、新しいAPI を使用して、2つのファイルオブジェクトが同じファイル参照であるかどうかを確認します。
.NETフレームワークで提供される類似のAPIはありますか?
MSDNで検索しましたが、私を啓発するものはありません。
シンプルにしたいのですが、ハード/シンボリックリンクと異なるスタイルのパスで問題が発生するファイル名で比較したくありません。 (例: \\?\ C:\
、 C:\
)。
私がやろうとしているのは、重複ファイルがリンクリストにドラッグアンドドロップされるのを防ぐことです。
解決
(1) (2) (3) (4)、JDK7が行う方法は、ファイルでGetFileInformationByHandle を実行し、dwVolumeSerialNumber、nFileIndexHigh、nFileIndexLowを比較します。
MSDNごと:
BY_HANDLE_FILE_INFORMATION構造体で返されるVolumeSerialNumberメンバーとFileIndexメンバーを比較して、2つのパスが同じターゲットにマッピングされているかどうかを判断できます。たとえば、2つのファイルパスを比較して、それらが同じディレクトリにマップされているかどうかを判断できます。
この関数は.NETによってラップされるとは思わないので、 P / Invoke 。
ネットワークファイルでは機能する場合と機能しない場合があります。 MSDNによると:
オペレーティングシステムの基盤となるネットワークコンポーネントと接続されているサーバーのタイプに応じて、GetFileInformationByHandle関数は失敗し、特定のファイルの部分的な情報または完全な情報を返す場合があります。
簡単なテストでは、SMB / Sambaを使用して接続されたLinuxシステム上のシンボリックリンクで期待どおり(同じ値)に動作するが、同じファイル(FileIndexは同じですが、VolumeSerialNumberは異なります)。
他のヒント
編集: @Rasmus Faber は Win32 APIのGetFileInformationByHandle 関数、およびこれはあなたが望むことをします、彼の詳細についてはをご覧ください。
必要な情報を提供するためにOS機能が必要だと思います。そうしないと、何をするにしても偽陰性が発生します。
たとえば、これらは同じファイルを参照していますか?
- \ server \ share \ path \ filename.txt
- \ server \ d $ \ temp \ path \ filename.txt
リストに重複したファイルが含まれないようにすることがどれほど重要かを調べてから、最善を尽くします。
とはいえ、Pathクラスにはいくつかの作業を実行できるメソッドがあります: Path.GetFullPath 、既存の構造に従って、少なくとも長い名前へのパスを展開します。その後、文字列を比較するだけです。しかし、それは絶対確実ではなく、私の例の上の2つのリンクを処理しません。
回答:文字列のベースパスと比較して、それらが同じファイルを指しているかどうかを判断できる、絶対確実な方法はありません。
主な理由は、一見無関係なパスが、ファイルシステムのリダイレクト(ジャンクション、シンボリックリンクなど)とまったく同じファイルを指すことがあるためです。例
" d:\ temp \ foo.txt" " c:\ othertemp \ foo.txt"
これらのパスは、同じファイルを指す可能性があります。この場合、2つのパスが同じファイルを指しているかどうかを判断するための基礎として、文字列比較関数が明らかに削除されます。
次のレベルは、OSファイル情報の比較です。 2つのパスのファイルを開き、ハンドル情報を比較します。 Windowsでは、GetFileInformationByHandleを使用してこれを実行できます。 Lucian Wischikはこのテーマに関する投稿はこちら。
しかし、このアプローチにはまだ問題があります。チェックを実行するユーザーアカウントが両方のファイルを読み取り用に開くことができる場合にのみ機能します。ユーザーが一方または両方のファイルを開けないようにすることができるアイテムが多数あります。含むが、これに限定されない...
- ファイルへの十分な権限がない
- ファイルのパスにあるディレクトリへの十分な権限がない
- ネットワークの切断など、最初のファイルを開いてから2番目のファイルを開くまでに発生するファイルシステムの変更。
これらの問題をすべて検討し始めると、Windowsが2つのパスが同じかどうかを判断する方法を提供しない理由を理解し始めます。簡単に答えられる質問ではありません。
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);
同じファイル名を何度も比較する必要がある場合は、それらの名前の正規化を検討することをお勧めします。
Unixシステムの下には、があります。パスを正規化するrealpath()関数。一般的に、複雑なパスがある場合、これが最善の策だと思います。ただし、ネットワーク接続を介してマウントされたボリュームでは失敗する可能性があります。
ただし、realpath()アプローチに基づいて、ネットワークボリュームを含む複数のボリュームをサポートする場合は、パス内の各ディレクトリ名をチェックし、ボリュームを参照するかどうかを確認して、ボリューム参照が両方のパスで同じです。つまり、マウントポイントは異なる場合があります(つまり、デスティネーションボリューム上のパスがそのボリュームのルートではない場合があります)。そのため、途中ですべての問題を解決するのはそれほど簡単ではありませんが、そもそも動作しますか?!)
ファイル名が適切に正規化されると、単純な文字列比較により正しい答えが得られます。
同じファイル名を何度も比較する必要がない場合、Rasmusの回答はおそらく最速の方法です。
常に両方でMD5エンコードを実行し、結果を比較できます。正確には効率的ではありませんが、自分でファイルを手動で比較するよりも簡単です。
C#で文字列をMD5する方法に関する投稿です。
>