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.txt
和three.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.txt
和three.txt
文件之间的相对路径。它们的目录没有公共前缀。
5. 总结
在这篇文章中,我们首先探讨了绝对路径和相对路径的主要区别。
接下来,我们根据两个文件的绝对路径构建了相对路径。我们首先从NIO2 API开始,并详细说明了构建相对路径的过程。
最后,我们试图使用java.net.URI
类达到同样的效果。我们发现,由于其限制,我们不能使用这个API完成所有的转换。
如往常一样,所有带有附加测试的示例可以在GitHub上找到。