Java, Linux: как определить, ссылаются ли два файла java.io.Files на один и тот же физический файл - PullRequest
9 голосов
/ 04 мая 2011

Я ищу эффективный способ определить, относятся ли два java.io.File к одному и тому же физическому файлу.Согласно документам, File.equals() должен выполнить работу:

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

Однако, учитывая раздел FAT32 (фактически контейнер TrueCrypt), которыймонтируется в / media / truecrypt1:

new File("/media/truecrypt1/File").equals(new File("/media/truecrypt1/file")) == false

Вы бы сказали, что это соответствует спецификации?И в этом случае, как обойти эту проблему?

Обновление: Благодаря комментаторам, для Java 7 я нашел java.io.Files.isSameFile(), который работает для меня.

Ответы [ 8 ]

14 голосов
/ 04 мая 2011

Ответ в комментарии @ Joachim, как правило, правильный.Чтобы определить, ссылаются ли два объекта File на один и тот же файл ОС, нужно использовать getCanonicalFile () или getCanonicalPath ().В javadoc говорится следующее:

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

Так что следующее должно работать:

File f1 = new File("/media/truecrypt1/File");  // different capitalization ...
File f2 = new File("/media/truecrypt1/file");  // ... but same OS file (on Windows)
if (f1.getCanonicalPath().equals(f2.getCanonicalPath())) {
    System.out.println("Files are equal ... no kittens need to die.");
}

Однако может показаться, что вы просматриваете файловую систему FAT32, смонтированную в UNIX/ Linux.AFAIK, Java не знает, что это происходит, и просто применяет общие правила UNIX / Linux для имен файлов ... которые дают неправильный ответ в этом сценарии.

Если это то, что действительно происходит,Я не думаю, что есть надежное решение в чистой Java 6. Однако,

  • Вы могли бы сделать что-то пушистое из JNI;например, получить номера дескриптора файла, а затем в собственном коде, использовать системный вызов fstat(2), чтобы узнать номера устройств и индексов двух файлов и сравнить их.

  • Java 7 java.nio.file.Path.equals(Object) выглядит так: может дать правильный ответ, если вы сначала вызовете resolve() по путям для разрешения символических ссылок.(Из javadoc немного неясно, будет ли каждая смонтированная файловая система в Linux соответствовать отдельному FileSystem объекту.)

  • В руководствах по Java 7 этот раздел при просмотре, если два Path объекта для одного и того же файла ... который рекомендует использовать java.nio.file.Files.isSameFile(Path, Path)


Вы бы сказали, что это соответствует спецификации??

Нет и да.

  • Нет в том смысле, что метод getCanonicalPath() не возвращает одно и то же значение для каждого существующего файла ОС ... это то, что вы ожидаете от чтения javadoc.

  • Да, в техническом смысле, кодовая база Java (не javadoc) является окончательной спецификацией ... как в теории, так и на практике.

3 голосов
/ 04 мая 2011

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

boolean isSame;
try {
   FileOutputStream file1 = new FileOutputStream (file1);
   FileOutputStream file2 = new FileOutputStream (file2);
   FileChannel channel1 = file1.getChannel();
   FileChannel channel2 = file2.getChannel();
   FileLock fileLock1 = channel1.tryLock();
   FileLock fileLock2 = channel2.tryLock();
   isSame = fileLock2 != null;
} catch(/*appropriate exceptions*/) {
   isSame = false;
} finally {
   fileLock1.unlock();
   fileLock2.unlock();
   file1.close();
   file2.close();
   ///cleanup etc...
}
System.out.println(file1 + " and " + file2 + " are " + (isSame?"":"not") + " the same");

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

2 голосов
/ 05 мая 2011

Метод Files.isSameFile был добавлен именно для этого вида использования, то есть вы хотите проверить, находят ли два неравных пути один и тот же файл.

2 голосов
/ 04 мая 2011

Существует вероятность того, что один и тот же файл имеет два пути (например, по сети \\localhost\file и \\127.0.0.1\file будут ссылаться на один и тот же файл с другим путем).Я хотел бы сравнить дайджесты обоих файлов, чтобы определить, идентичны они или нет.Примерно так:

public static void main(String args[]) {
    try {
        File f1 = new File("\\\\79.129.94.116\\share\\bots\\triplon_bots.jar");
        File f2 = new File("\\\\triplon\\share\\bots\\triplon_bots.jar");
        System.out.println(f1.getCanonicalPath().equals(f2.getCanonicalPath()));
        System.out.println(computeDigestOfFile(f1).equals(computeDigestOfFile(f2)));
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

private static String computeDigestOfFile(File f) throws Exception {
    MessageDigest md = MessageDigest.getInstance("MD5");
    InputStream is = new FileInputStream(f);
    try {
        is = new DigestInputStream(is, md);
        byte[] buffer = new byte[1024];
        while(is.read(buffer) != -1) {
            md.update(buffer);
        }
    }
    finally {
        is.close();
    }
    return new BigInteger(1,md.digest()).toString(16);
}

Он выводит

false
true

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

1 голос
/ 04 мая 2011

Традиционный способ Unix для проверки того, ссылаются ли два имени файла на один и тот же базовый объект файловой системы, - это stat их и проверка, имеют ли они одну и ту же пару [dev,ino].

Это не предполагает избыточных монтировок, однако. Если это разрешено, вы должны поступить иначе.

1 голос
/ 04 мая 2011

Вы можете попробовать Runtime.exec () из

ls -i /fullpath/File # extract the inode number.
df /fullpath/File # extract the "Mounted on" field.

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

Или даже

bash test "file1" -ef "file2"

FILE1 и FILE2 имеют одинаковые номера устройств и индексов

1 голос
/ 04 мая 2011

Документ API equals() говорит (сразу после вашей цитаты):

В системах UNIX алфавитный регистр важны при сравнении путей; на Microsoft Windows систем это не так.

1 голос
/ 04 мая 2011

В * nix системах корпус имеет значение . file не совпадает с File или fiLe.

...