Лучший способ определить, ссылаются ли два пути на один и тот же файл в C#

StackOverflow https://stackoverflow.com/questions/410705

  •  03-07-2019
  •  | 
  •  

Вопрос

В готовящемся Java7 есть новый API чтобы проверить, являются ли два файловых объекта одной и той же ссылкой на файл.

Предусмотрены ли аналогичные API в .NET Framework?

Я поискал его по MSDN, но ничего не просветило меня.

Я хочу, чтобы это было просто, но я не хочу сравнивать по имени файла, что вызовет проблемы с жесткими / символическими ссылками и другим стилем пути.(например, \\?\C:\, C:\).

Что я собираюсь сделать, так это просто предотвратить перетаскивание дублированного файла в мой список ссылок.

Это было полезно?

Решение

Насколько я могу судить (1) (2) (3) (4), способ, которым JDK7 делает это, заключается в вызове GetFileInformationByHandle - дескриптор на файлах и сравнивая dwVolumeSerialNumber, nFileIndexHigh и nFileIndexLow.

Согласно MSDN:

Вы можете сравнить элементы VolumeSerialNumber и FileIndex, возвращаемые в структуре BY_HANDLE_FILE_INFORMATION, чтобы определить, соответствуют ли два пути одному и тому же целевому объекту;например, вы можете сравнить пути к двум файлам и определить, соответствуют ли они одному и тому же каталогу.

Я не думаю, что эта функция обернута .NET, поэтому вам придется использовать P/Вызывать.

Это может работать, а может и не работать для сетевых файлов.Согласно MSDN:

В зависимости от базовых сетевых компонентов операционной системы и типа подключенного сервера функция GetFileInformationByHandle может завершиться сбоем, вернуть частичную информацию или полную информацию для данного файла.

Быстрый тест показывает, что он работает как ожидалось (те же значения) с символической ссылкой в системе Linux, подключенной с помощью SMB / Samba, но что он не может определить, что файл является одним и тем же при доступе с использованием разных общих ресурсов, которые указывают на один и тот же файл (FileIndex тот же, но VolumeSerialNumber отличается).

Другие советы

Редактировать:Обратите внимание , что @Rasmus Faber упоминает о GetFileInformationByHandle - дескриптор функция в Win32 api, и это делает то, что вы хотите, проверьте и поддержите его ответ для получения дополнительной информации.


Я думаю, вам нужна функция операционной системы, которая предоставляла бы вам нужную информацию, в противном случае, что бы вы ни делали, у нее будут ложноотрицательные результаты.

Например, относятся ли они к одному и тому же файлу?

  • \server\share\path\filename.txt
  • \сервер\d$ emp\path\filename.txt

Я бы изучил, насколько важно для вас, чтобы в вашем списке не было дубликатов файлов, а затем просто приложил максимум усилий.

Сказав это, в классе Path есть метод, который может выполнить часть работы: Путь.GetFullPath, это, по крайней мере, расширит путь к длинным именам, в соответствии с существующей структурой.После этого вы просто сравниваете строки.Однако это не будет надежным способом и не будет обрабатывать две ссылки, приведенные выше в моем примере.

Ответ:Не существует надежного способа, которым вы могли бы сравнить со строковыми базовыми путями, чтобы определить, указывают ли они на один и тот же файл.

Основная причина заключается в том, что, казалось бы, несвязанные пути могут указывать на тот же файл, что и перенаправления файловой системы (переходы, символические ссылки и т.д.).Например

"d: emp\foo.txt" "c:\othertemp\foo.txt"

Эти пути потенциально могут указывать на один и тот же файл.Этот случай явно исключает любую функцию сравнения строк в качестве основы для определения, указывают ли два пути на один и тот же файл.

Следующий уровень - это сравнение информации о файле операционной системы.Откройте файл по двум путям и сравните информацию о дескрипторе.В Windows это можно сделать с помощью GetFileInformationByHandle.Лучан Вишик проделал отличную работу. Публикация по этому вопросу здесь.

Однако с таким подходом все еще существует проблема.Это работает только в том случае, если учетная запись пользователя, выполняющая проверку, может открыть оба файла для чтения.Существует множество элементов, которые могут помешать пользователю открыть один или оба файла.Включая , но не ограничиваясь этим ...

  • Отсутствие достаточных разрешений для доступа к файлу
  • Отсутствие достаточных прав доступа к каталогу в пути к файлу
  • Изменение файловой системы, которое происходит между открытием первого файла и второго, например, отключение от сети.

Когда вы начинаете рассматривать все эти проблемы, вы начинаете понимать, почему Windows не предоставляет метода для определения того, совпадают ли два пути.Просто ответить на этот вопрос непросто.

Вот реализация на C # IsSameFile используя GetFileInformationByHandle:

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);
}

Проходимость.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(), если вы хотите поддерживать несколько томов, включая сетевые тома, вы могли бы написать свою собственную функцию, которая проверяет каждое имя каталога в пути и, если оно ссылается на том, определяет, совпадает ли ссылка на том в обоих путях.При этом точка монтирования может отличаться (т.е.путь к целевому тому может не быть корневым для этого тома), поэтому решить все проблемы на этом пути не так просто, но это определенно возможно (иначе как бы это работало в первую очередь?!)

Как только имена файлов должным образом канонизированы, простое сравнение строк дает вам правильный ответ.

Ответ Расмуса, вероятно, самый быстрый способ, если вам не нужно сравнивать одни и те же имена файлов снова и снова.

Вы всегда можете выполнить кодирование MD5 для обоих и сравнить результат.Не совсем эффективно, но проще, чем вручную сравнивать файлы самостоятельно.

Вот сообщение о как создать MD5 строку в C#.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top