getCanonicalPath和toRealPath的区别

15

File.getCanonicalPath()和File.toPath().toRealPath()是否会产生不同的结果?它们似乎都做类似的事情,但文档从未明确说明它们应该做相同的事情。是否有一些边界情况我可以更倾向于使用其中一种方法?而File.getAbsolutePath()与Path.toAbsolutePath()又是如何运作的,它们是否应该以相同的方式工作?


请参见https://dev59.com/znNA5IYBdhLWcg3wEpkU - Lucas Holt
1
@LucasHolt:这是在谈论一个完全不同的API。这是Java.NIO。 - Makoto
是的,但路径处理方式是相同的。 - Lucas Holt
有些微妙的细节超越了路径本身的含义。其中一种方法实际上决定了如何处理路径。 - Makoto
5个回答

14
结论:
  • getAbsolutePathgetPath 没有验证,因此永远不会失败
  • getCanonicalPath 当驱动器字母无效或与当前文件夹不同时,可能会得出无效结果
  • toPath().toRealPath() 是在检查有效性,但是需要文件存在并且可以遵循或不遵循符号链接
  • toPath() 足够安全,不需要文件存在
  • .toPath().toAbsolutePath().normalize() 是最好的,而且不需要文件存在

我在Windows中进行了类似于@John的测试。

  @Test
  public void testCanonical() throws IOException {
    test("d:tarGet\\..\\Target", "File exist and drive letter is on the current one");
    test("d:tarGet\\..\\Target\\.\\..\\", "File exist and drive letter is on the current one, but parent of current drive should exist");
    test("d:tarGet\\non-existent\\..\\..\\Target\\.\\..\\", "Relative path contains non-existent file");
    test("d:target\\\\file", "Double slash");
    test("c:tarGet\\..\\Target\\.", "File doesn't exist and drive letter is on different drive than the current one");
    test("l:tarGet\\..\\Target\\.\\..\\", "Drive letter doesn't exist");
    test("za:tarGet\\..\\Target\\.\\..\\", "Drive letter is double so not valid");
    test("d:tarGet|Suffix", "Path contains invalid chars in windows (|)");
    test("d:tarGet\u0000Suffix", "Path contains invalid chars in both linux and windows (\\0)");
  }

  private void test(String filename, String message) throws IOException {
    java.io.File file = new java.io.File(filename);
    System.out.println("Use:  " + filename + " -> " + message);
    System.out.println("F-GET:     " + Try.of(() -> file.getPath()));
    System.out.println("F-ABS:     " + Try.of(() -> file.getAbsolutePath()));
    System.out.println("F-CAN:     " + Try.of(() -> file.getCanonicalPath()));
    System.out.println("P-TO:      " + Try.of(() -> file.toPath()));
    System.out.println("P-ABS:     " + Try.of(() -> file.toPath().toAbsolutePath()));
    System.out.println("P-NOR:     " + Try.of(() -> file.toPath().normalize()));
    System.out.println("P-NOR-ABS: " + Try.of(() -> file.toPath().normalize().toAbsolutePath()));
    System.out.println("P-ABS-NOR: " + Try.of(() -> file.toPath().toAbsolutePath().normalize()));
    System.out.println("P-REAL:    " + Try.of(() -> file.toPath().toRealPath()));
    System.out.println("");
  }

结果如下:
Use:  d:tarGet\..\Target -> File exist and drive letter is on the current one
F-GET:     Success(d:tarGet\..\Target)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\..\Target)
F-CAN:     Success(D:\home\raiser\work\restfs\target)
P-TO:      Success(d:tarGet\..\Target)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\..\Target)
P-NOR:     Success(d:Target)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\Target)
P-ABS-NOR: Success(D:\home\raiser\work\restfs\Target)
P-REAL:    Success(D:\home\raiser\work\restfs\target)

Use:  d:tarGet\..\Target\.\..\ -> File exist and drive letter is on the current one, but parent of current drive should exist
F-GET:     Success(d:tarGet\..\Target\.\..)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Success(d:tarGet\..\Target\.\..)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\..\Target\.\..)
P-NOR:     Success(d:)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\)
P-ABS-NOR: Success(D:\home\raiser\work\restfs)
P-REAL:    Success(D:\home\raiser\work\restfs)

Use:  d:tarGet\non-existent\..\..\Target\.\..\ -> Relative path contains non-existent file
F-GET:     Success(d:tarGet\non-existent\..\..\Target\.\..)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\non-existent\..\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Success(d:tarGet\non-existent\..\..\Target\.\..)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\non-existent\..\..\Target\.\..)
P-NOR:     Success(d:)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\)
P-ABS-NOR: Success(D:\home\raiser\work\restfs)
P-REAL:    Success(D:\home\raiser\work\restfs)

Use:  d:target\\file -> Double slash
F-GET:     Success(d:target\file)
F-ABS:     Success(d:\home\raiser\work\restfs\target\file)
F-CAN:     Success(D:\home\raiser\work\restfs\target\file)
P-TO:      Success(d:target\file)
P-ABS:     Success(D:\home\raiser\work\restfs\target\file)
P-NOR:     Success(d:target\file)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\target\file)
P-ABS-NOR: Success(D:\home\raiser\work\restfs\target\file)
P-REAL:    Failure(java.nio.file.NoSuchFileException: D:\home\raiser\work\restfs\target\file)

Use:  c:tarGet\..\Target\. -> File doesn't exist and drive letter is on different drive than the current one
F-GET:     Success(c:tarGet\..\Target\.)
F-ABS:     Success(c:\\tarGet\..\Target\.)
F-CAN:     Success(C:\Target)
P-TO:      Success(c:tarGet\..\Target\.)
P-ABS:     Success(C:\tarGet\..\Target\.)
P-NOR:     Success(c:Target)
P-NOR-ABS: Success(C:\Target)
P-ABS-NOR: Success(C:\Target)
P-REAL:    Failure(java.nio.file.NoSuchFileException: C:\Target)

Use:  l:tarGet\..\Target\.\..\ -> Drive letter doesn't exist
F-GET:     Success(l:tarGet\..\Target\.\..)
F-ABS:     Success(l:\tarGet\..\Target\.\..)
F-CAN:     Success(L:\)
P-TO:      Success(l:tarGet\..\Target\.\..)
P-ABS:     Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-NOR:     Success(l:)
P-NOR-ABS: Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-ABS-NOR: Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-REAL:    Failure(java.io.IOException: Unable to get working directory of drive 'L')

Use:  za:tarGet\..\Target\.\..\ -> Drive letter is double so not valid
F-GET:     Success(za:tarGet\..\Target\.\..)
F-ABS:     Success(D:\home\raiser\work\restfs\za:tarGet\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-ABS:     Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-NOR:     Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-NOR-ABS: Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-ABS-NOR: Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-REAL:    Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)

Use:  d:tarGet|Suffix -> Path contains invalid chars in windows (|)
F-GET:     Success(d:tarGet|Suffix)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet|Suffix)
F-CAN:     Failure(java.io.IOException: The filename, directory name, or volume label syntax is incorrect)
P-TO:      Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-ABS:     Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-NOR:     Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-NOR-ABS: Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-ABS-NOR: Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-REAL:    Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)

3

一个规范路径是绝对且唯一的, 但在不同的系统上具有不同的含义。

一个规范路径名既是绝对的又是唯一的。规范形式的精确定义取决于系统。

真实路径是相对于系统而言的实际路径。您还必须传递是否处理符号链接,其中使用canonicalPath隐式处理。

该方法的精确定义取决于实现,但通常它从此路径派生出一个绝对路径,该路径定位与此路径相同的文件,但名称元素表示目录和文件的实际名称。例如,如果文件系统上的文件名比较不区分大小写,则名称元素表示名称的实际大小写。此外,结果路径已删除冗余名称元素。

所以,是的,这两种方法可以返回不同的结果,但这实际上取决于你的系统。如果你需要一个唯一的东西,那么canonicalPath是你最安全的选择,即使它不是一个Path

1
是的,谢谢,但这两个定义本质上都表示:我们将对您的文件/路径进行某些操作,使其变得“真实”或“规范”,但我们并没有告诉您确切的操作方式,甚至没有告诉您我们是否在两种方法中使用相同的操作(假设我们使用toRealPath()进行符号链接扩展)。我觉得这很令人沮丧,也不太专业。文档甚至没有说明这种工作方式是仅取决于操作系统或文件系统,还是可能取决于Java实现。 - jpp1

2
我从我的测试中发现:
- 如果文件不存在,Path.toRealPath()将抛出java.nio.file.NoSuchFileException(Javadoc:返回现有文件的实际路径)。 - 如果文件不存在,File.getCanonicalPath()不会抛出异常(仅在文件名本身无效且包含'\0'字符时才会抛出IOException)。
因此,如果您要在实际创建文件之前进行路径检查,则前者不适用。
您是正确的,两种方法的Javadoc有点浅。

1
当然,下面的示例展示了一些差异。如果文件不存在,getCanonicalPath将抛出异常。
getCanonicalPath以其规范或最简单的形式返回路径(来源于http://www.merriam-webster.com/dictionary/canonical%20form)。
import java.io.File;

public class FileExample {

    public static void main(String[] args) throws Exception {
            File file = new File("/TEMP/../TEMP/myfile.txt");
            System.out.println("ABS: " + file.getAbsolutePath());
            System.out.println(" TO: " + file.toPath());
            System.out.println("GET: " + file.getPath());
            System.out.println("CAN: " + file.getCanonicalPath());
        }
    }


ABS: C:\TEMP\..\TEMP\myfile.txt
 TO: \TEMP\..\TEMP\myfile.txt
GET: \TEMP\..\TEMP\myfile.txt
CAN: C:\TEMP\myfile.txt

我对Path.toRealPath()很感兴趣,它似乎与File.getCanonicalPath()做了非常相似的事情。 - jpp1
1
File#getCanonicalPath() 不需要文件存在,这是错误的观念。只有 Path#getRealPath() 需要文件存在。anre 的回答是正确的。 - radlan

1
API 表明规范路径通常会消除冗余并解析符号链接等内容。
在 UNIX 机器上尝试以下操作:
File file = new File("../test.txt"); // execute from /tmp/java/example
file.getAbsolutePath();  // evaluates to /tmp/java/example/../test.txt
file.getCanonicalPath(); // evaluates to /tmp/java/test.txt

"File和Path的区别在于,Path是新的NIO API的一部分,具有许多改进和更灵活的功能。例如,您可以使用NIO(参见https://github.com/google/jimfs)替换文件系统的实现,而java.io.File则强制您在主机文件系统上操作。"

我知道我的问题涉及Path.toRealPath()和File.getCanonicalPath()之间的区别,因为它们看起来做的事情非常相似。 - jpp1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接