Melhor maneira de determinar se referência dois caminho para mesmo arquivo em C #

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

  •  03-07-2019
  •  | 
  •  

Pergunta

No próximo Java7, há um novo API para verificar se o objeto de dois arquivos são as mesmas referência de arquivo.

Existem API semelhante fornecido no .NET framework?

Eu tenho busca-lo ao longo MSDN mas nada me esclarecer.

Eu quero-o simples, mas eu não quero comparar por nome de arquivo que irá causar problemas com links de disco rígido / simbólicos e estilo diferente de caminho. (Por exemplo \\?\C:\, C:\).

O que eu vou fazer é apenas evitar arquivo duplicado sendo arrastar e caiu para o meu linklist.

Foi útil?

Solução

Tanto quanto eu posso ver (1) (2) (3) (4) , a maneira JDK7 faz isso, é chamando GetFileInformationByHandle nos arquivos e comparando dwVolumeSerialNumber, nFileIndexHigh e nFileIndexLow.

Por MSDN:

Você pode comparar o VolumeSerialNumber e membros FileIndex retornados na estrutura BY_HANDLE_FILE_INFORMATION para determinar se dois caminhos mapeados para o mesmo alvo; por exemplo, você pode comparar dois caminhos de arquivo e determinar se eles são mapeados para o mesmo diretório.

Eu não acho que esta função é envolto por .NET, assim você terá que usar P / Invoke .

Ele pode ou não funcionar para arquivos de rede. De acordo com MSDN:

Dependendo dos componentes de rede subjacentes do sistema operacional e do tipo de servidor conectado, a função GetFileInformationByHandle pode falhar, retornar informações parciais, ou informação completa para o arquivo de dados.

A mostra teste rápido que ele funciona como esperado (mesmos valores) com um link simbólico em um sistema Linux conectados usando SMB / Samba, mas que não podem detectar que um arquivo é o mesmo quando acessado usando ações diferentes que apontam para a mesmo arquivo (FileIndex é o mesmo, mas difere VolumeSerialNumber).

Outras dicas

Editar : Note que @Rasmus Faber menciona a função GetFileInformationByHandle na API Win32, e este faz o que quiser , verificar e upvote sua resposta para obter mais informações.


Eu acho que você precisa de uma função de OS para lhe dar as informações que deseja, caso contrário ele vai ter alguns falsos negativos Faça o que fizer.

Por exemplo, se referem ao mesmo arquivo?

  • \ server \ share \ path \ filename.txt
  • \ server \ d $ \ temp \ caminho \ filename.txt

Eu iria examinar como é crítico para você não ter arquivos duplicados em sua lista, e em seguida, basta fazer alguma melhor esforço.

Dito isto, há um método na classe Caminho que pode fazer parte do trabalho: Path.GetFullPath , ele irá, pelo menos, expandir o caminho para nomes longos, de acordo com a estrutura existente. Depois que você acabou de comparar as cordas. Não vai ser infalível, porém, e não vai lidar com os dois links acima no meu exemplo.

Resposta: Não há nenhuma maneira infalível em que você pode comparar com caminhos de base corda para determinar se eles apontam para o mesmo arquivo.

A principal razão é que os caminhos aparentemente não relacionados podem apontar para o mesmo arquivo exato fazer para redirecionamentos do sistema de arquivos (junções, links simbólicos, etc ...). Por exemplo

"d: \ temp \ foo.txt" "C: \ othertemp \ foo.txt"

Esses caminhos potencialmente pode apontar para o mesmo arquivo. Neste caso elimina claramente qualquer função de comparação de string como uma base para determinar se dois caminhos apontam para o mesmo arquivo.

O próximo nível é comparar as informações do arquivo OS. Abra o arquivo para dois caminhos e comparar as informações alça. Em janelas isso pode ser feito com GetFileInformationByHandle. Lucian Wischik fez um excelente postar sobre este assunto aqui.

Há ainda um problema com essa abordagem embora. Ele só funciona se a conta de usuário que executa a verificação é capaz de abrir os dois arquivos para a leitura. Existem inúmeros itens que podem impedir um usuário de uma abertura ou ambos os arquivos. Incluindo, mas não limitado a ...

  • A falta de permissões suficientes para arquivo
  • falta de permissões suficientes para um diretório no caminho do arquivo
  • mudança
  • Sistema de arquivos que ocorre entre a abertura do primeiro arquivo e o segundo, como uma desconexão da rede.

Quando você começar a olhar para todos esses problemas que você começar a entender porque o Windows não fornece um método para determinar se dois caminhos são os mesmos. Não é apenas uma possível pergunta fácil / a resposta.

Aqui é um C # implementação de IsSameFile usando 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);
}

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

Primeiro pensei que é realmente fácil, mas este não trabalho:

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

Talvez esta biblioteca ajuda http://www.codeplex.com/FileDirectoryPath . Eu não usei isso mesmo.

Editar: Veja este exemplo no site:

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

Se você precisa comparar os mesmos nomes de arquivos mais de uma e outra vez, eu sugiro que você olhar para canonalizing esses nomes.

Em um sistema Unix, há a realpath () função que canonalizes seu caminho. Eu acho que é geralmente a melhor aposta se você tem um complexo caminho. No entanto, é provável que falhe em volumes montados através de conexões de rede.

No entanto, com base na abordagem realpath (), se você quiser apoiar vários volumes, incluindo os volumes de rede, você pode escrever sua própria função que verifica cada nome de diretório em um caminho e se faz referência a um volume, em seguida, determinar se a referência de volume em ambos os caminhos, é a mesma. Dito isto, o ponto de montagem pode ser diferente (ou seja, o caminho no volume de destino não pode ser a raiz desse volume) por isso não é tão fácil de resolver todos os problemas ao longo do caminho, mas é definitivamente possível (caso contrário, como será que funcionará em primeiro lugar?!)

Uma vez que os nomes de arquivo corretamente canonalized uma comparação de string simples dá-lhe a resposta correta.

Rasmus resposta é provavelmente a maneira mais rápida se você não precisa comparar os mesmos nomes de arquivos e outra vez.

Você sempre pode executar uma codificação MD5 em ambos e comparar o resultado. Não é exatamente eficiente, mas mais fácil de comparar manualmente os arquivos de si mesmo.

Aqui está um post sobre como MD5 uma string em C # .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top