1. 概述

在这个教程中,我们将学习如何在Java中根据两个绝对路径构建相对路径。我们将重点关注Java的两个内置API:新的I/O(NIO2)Path API和URI类。

2. 绝对路径与相对路径

在开始之前,我们先快速回顾一下。在本文的所有示例中,我们将使用用户主目录中的相同文件结构:

/ (root)
|-- baeldung
    \-- bar
    |   |-- one.txt
    |   |-- two.txt
    \-- foo
        |-- three.txt

绝对路径描述了一个位置,无论当前工作目录如何,都从根节点开始。 这是我们的文件的绝对路径:

one.txt -> /baeldung/bar/one.txt
two.txt -> /baeldung/bar/two.txt
three.txt -> /baeldung/foo/three.txt

即使更改工作目录,绝对路径始终保持不变。

另一方面,相对路径描述目标节点相对于其源的位置。如果我们处于baeldung目录下,让我们看看文件的相对路径:

one.txt -> ./bar/one.txt
two.txt -> ./bar/two.txt
three.txt -> ./foo/three.txt

现在,让我们移动到bar子目录,再次检查相对路径:

one.txt -> ./one.txt
two.txt -> ./two.txt
three.txt -> ../foo/three.txt

如我们所见,结果略有不同。我们必须记住,如果修改源上下文,相对值可能会改变,而绝对路径是恒定的。绝对路径是相对路径的一个特例,其中源节点是系统的根。

3. NIO2 API

现在我们了解了绝对路径和相对路径的工作原理,是时候了解NIO2 API了。我们知道,NIO2 API是在Java 7发布时引入的,它改进了旧的I/O API,后者存在许多问题。使用这个API,我们将尝试确定由绝对路径描述的两个文件之间的相对路径。

首先,我们为文件创建Path对象:

Path pathOne = Paths.get("/baeldung/bar/one.txt");
Path pathTwo = Paths.get("/baeldung/bar/two.txt");
Path pathThree = Paths.get("/baeldung/foo/three.txt");

为了在源和给定节点之间构建相对路径,我们可以使用Path类提供的relativize(Path)方法:

Path result = pathOne.relativize(pathTwo);

assertThat(result)
  .isRelative()
  .isEqualTo(Paths.get("../two.txt"));

如我们所见,结果显然是一个相对路径。这是正确的吗?特别是开头有父操作符(../*)?

我们必须记住,相对路径可以从任何类型的节点开始指定,可以是目录或文件。当我们使用命令行界面(CLI)或文件浏览器时,我们通常处理目录。在这种情况下,所有相对路径都是基于当前工作目录计算的。

在我们的例子中,我们创建了一个指向特定文件的Path。因此,我们首先需要到达文件的父目录,然后前往第二个文件。总的来说,结果是正确的。

如果我们想使结果相对于源目录,我们可以使用getParent()方法:

Path result = pathOne.getParent().relativize(pathTwo);

assertThat(result)
  .isRelative()
  .isEqualTo(Paths.get("two.txt"));

需要注意的是,Path对象可能指向任何文件或目录。如果我们构建更复杂的逻辑,需要进行额外的检查。

最后,让我们检查one.txtthree.txt文件之间的相对路径:

Path resultOneToThree = pathOne.relativize(pathThree);
Path resultThreeToOne = pathThree.relativize(pathOne);

assertThat(resultOneToThree)
  .isRelative()
  .isEqualTo(Paths.get("..\..\foo\three.txt"));
assertThat(result)
  .isRelative()
  .isEqualTo(Paths.get("..\..\bar\one.txt"));

这个快速测试证实了相对路径是上下文相关的。尽管绝对路径仍然相同,但当我们一起交换源和目标节点时,相对路径会有所不同。

4. java.net.URI API

在检查完NIO2 API后,我们转向java.net.URI类。我们知道,URI(统一资源标识符)是一种字符字符串,它允许我们标识任何可以使用的资源,包括文件操作

让我们为我们的文件构建URI对象:

URI uriOne = pathOne.toURI();
// URI uriOne = URI.create("file:///baeldung/bar/one.txt")
URI uriTwo = pathTwo.toURI();
URI uriThree = pathThree.toURI();

我们可以使用String构造URI对象,或者将先前创建的Path转换为URI

与以前一样,URI类也提供了relativize(URI)方法。让我们使用它来构建相对路径:

URI result = uriOne.relativize(uriTwo);

assertThat(result)
  .asString()
  .contains("file:///baeldung/bar/two.txt");

结果并不符合预期,相对路径没有正确构建。要回答这个问题,我们需要查看该类的官方文档

此方法仅在源URI是目标URI的前缀时返回相对值。 否则,它返回目标值。因此,我们无法在文件节点之间构建相对路径。在这种情况下,一个URI永远不会前缀另一个。

为了得到一个相对路径,我们可以将我们的源URI设置为第一个文件的目录:

URI uriOneParent = pathOne.getParent().toUri(); // file:///baeldung/bar/
URI result = uriOneParent.relativize(uriTwo);

assertThat(result)
  .asString()
  .contains("two.txt");

现在,源节点是目标前缀,所以结果被正确计算。由于方法的限制,我们无法使用URI方法确定one.txt/two.txtthree.txt文件之间的相对路径。它们的目录没有公共前缀。

5. 总结

在这篇文章中,我们首先探讨了绝对路径和相对路径的主要区别。

接下来,我们根据两个文件的绝对路径构建了相对路径。我们首先从NIO2 API开始,并详细说明了构建相对路径的过程。

最后,我们试图使用java.net.URI类达到同样的效果。我们发现,由于其限制,我们不能使用这个API完成所有的转换。

如往常一样,所有带有附加测试的示例可以在GitHub上找到