本文我们学习如何使用java 中的 new I/O(NIO2) Path api。NIO2中的Path API构成了Java 7附带的主要新功能之一,特别是文件系统API和文件API的一个子集。
NIO2支持的类在java.nio.file 包中。所以项目需要使用Path API,仅需要导入包:
import java.nio.file.*;
因为本文示例代码可能运行在不同环境,所以我们针对用户目录进行操作,确保在所有操作系统中都可运行且结果一致:
private static String HOME = System.getProperty("user.home");
该变量所指位置可以在任何环境中都有效。Paths类是所有和文件系统路径相关操作的主入口点,其提供了创建和维护文件和目录的路径功能。
需要注意的是,路径操作本质上是语法层面的,无论其操作成功与否对其对底层文件系统没有影响。这意味着传递不存在路径作为路径操作参数与其成功与否无关。
本节我们介绍主要路径操作的常用语法。顾名思义,Path类是对文件系统路径的编程表示。包含文件名称和目录列表的Path对象用于构建构建路径,实现检测、定位和操作文件。
java.nio.file.Paths助手类是Path的复数形式,用于创建Path对象,其有两个static方法创建Path对象:
Path path = Paths.get("path string");
无论字符串使用正斜杠或反斜杠都没关系,api会与根据底层操作保持一致。
也可以使用java.net.URI对象创建path:
Path path = Paths.get(URI object);
现在我们继续看看它们的实际应用。
从路径字符串创建Path对象:
@Test
public void givenPathString_whenCreatesPathObject_thenCorrect() {
Path p = Paths.get("/articles/baeldung");
assertEquals("\\articles\\baeldung", p.toString());
}
get API除了第一部分(在本例中是articles和baeldung)之外,还可以接受path字符串部分的变量参数参数。如果提供多个参数代替完整路径字符串,无需包括名称之间的分隔符(斜线):
@Test
public void givenPathParts_whenCreatesPathObject_thenCorrect() {
Path p = Paths.get("/articles", "baeldung");
assertEquals("\\articles\\baeldung", p.toString());
}
你可以认为Path对象是一组名称元素序列。比如path字符串E:\baeldung\articles\java 有三个名称元素组成,分别为baeldung、articles、java 。目录结果的最高元素位于索引0,即baeldung。目录最后的元素位于索引n-1,n是path中元素数量。最后元素被称为文件名,无论其是否为文件:
@Test
public void givenPath_whenRetrievesFileName_thenCorrect() {
Path p = Paths.get("/articles/baeldung/logs");
Path fileName = p.getFileName();
assertEquals("logs", fileName.toString());
}
path对象的getName方法可以根据索引返回名称元素:
@Test
public void givenPath_whenRetrievesNameByIndex_thenCorrect() {
Path p = Paths.get("/articles/baeldung/logs");
Path name0 = p.getName(0);
Path name1 = p.getName(1);
Path name2 = p.getName(2);
assertEquals("articles", name0.toString());
assertEquals("baeldung", name1.toString());
assertEquals("logs", name2.toString());
}
或者通过索引范围返回自序列:
@Test
public void givenPath_whenCanRetrieveSubsequenceByIndex_thenCorrect() {
Path p = Paths.get("/articles/baeldung/logs");
Path subPath1 = p.subpath(0,1);
Path subPath2 = p.subpath(0,2);
assertEquals("articles", subPath1.toString());
assertEquals("articles\\baeldung", subPath2.toString());
assertEquals("articles\\baeldung\\logs", p.subpath(0, 3).toString());
assertEquals("baeldung", p.subpath(1, 2).toString());
assertEquals("baeldung\\logs", p.subpath(1, 3).toString());
assertEquals("logs", p.subpath(2, 3).toString());
}
每个path都关联一个父path,或者没有父path则为null。path对象的父对象有path对象的根组件(如果有的话)和路径中的每个元素(文件名除外)组成。/a/b/c的父对象是/a/b,/a的父对象是null:
@Test
public void givenPath_whenRetrievesParent_thenCorrect() {
Path p1 = Paths.get("/articles/baeldung/logs");
Path p2 = Paths.get("/articles/baeldung");
Path p3 = Paths.get("/articles");
Path p4 = Paths.get("/");
Path parent1 = p1.getParent();
Path parent2 = p2.getParent();
Path parent3 = p3.getParent();
Path parent4 = p4.getParenth();
assertEquals("\\articles\\baeldung", parent1.toString());
assertEquals("\\articles", parent2.toString());
assertEquals("\\", parent3.toString());
assertEquals(null, parent4);
}
我们也可以直接获得path对象的根元素:
@Test
public void givenPath_whenRetrievesRoot_thenCorrect() {
Path p1 = Paths.get("/articles/baeldung/logs");
Path p2 = Paths.get("c:/articles/baeldung/logs");
Path root1 = p1.getRoot();
Path root2 = p2.getRoot();
assertEquals("\\", root1.toString());
assertEquals("c:\\", root2.toString());
}
很多文件系统使用".“表示当前目录,使用”…"表示父目录。你可能会遇到path包含多余的目录信息,举例,请看下面字符串:
/baeldung/./articles
/baeldung/authors/../articles
/baeldung/articles
三者都指向相同的位置 /baeldung/articles 。前面两个有多余信息,最后一个没有。规范化path即删除多余信息。Path.normalize()操作可以实现该功能。下面示例很好理解:
@Test
public void givenPath_whenRemovesRedundancies_thenCorrect1() {
Path p = Paths.get("/home/./baeldung/articles");
Path cleanPath = p.normalize();
assertEquals("\\home\\baeldung\\articles", cleanPath.toString());
}
另一个示例:
@Test
public void givenPath_whenRemovesRedundancies_thenCorrect2() {
Path p = Paths.get("/home/baeldung/../articles");
Path cleanPath = p.normalize();
assertEquals("\\home\\articles", cleanPath.toString());
}
Path的转换操作可以把path转成可选的表现格式。为了转换path至浏览器可以打开的字符串,需要使用toUri方法:
@Test
public void givenPath_whenConvertsToBrowseablePath_thenCorrect() {
Path p = Paths.get("/home/baeldung/articles.html");
URI uri = p.toUri();
assertEquals(
"file:///E:/home/baeldung/articles.html",
uri.toString());
}
我们也可以转换path至决定路径形式。toAbsolutePath 方法基于文件系统的缺省目录返回决定路径。
@Test
public void givenPath_whenConvertsToAbsolutePath_thenCorrect() {
Path p = Paths.get("/home/baeldung/articles.html");
Path absPath = p.toAbsolutePath();
assertEquals(
"E:\\home\\baeldung\\articles.html",
absPath.toString());
}
然而,当path被检测已经是绝对路径时,方法会直接返回:
@Test
public void givenAbsolutePath_whenRetainsAsAbsolute_thenCorrect() {
Path p = Paths.get("E:\\home\\baeldung\\articles.html");
Path absPath = p.toAbsolutePath();
assertEquals(
"E:\\home\\baeldung\\articles.html",
absPath.toString());
}
我们也能调用toRealPath方法转换任何path至其真实路径。该方法通过映射元素至文件系统中实际目录和文件。前面使用变量存储当前登录用户在文件系统中的用户目录:
@Test
public void givenExistingPath_whenGetsRealPathToFile_thenCorrect() {
Path p = Paths.get(HOME);
Path realPath = p.toRealPath();
assertEquals(HOME, realPath.toString());
}
该测试么有真正告诉我们关于该操作的行为。最明显的结果是如果路径不存在,则会抛IOException异常。
如果没有更好的方法来解释这一点,看看下面的测试,它试图将不存在的路径转换为真实的路径:
@Test(expected = NoSuchFileException.class)
public void givenInExistentPath_whenFailsToConvert_thenCorrect() {
Path p = Paths.get("E:\\home\\baeldung\\articles.html");
p.toRealPath();
}
如果我们捕获IOException也会成功,因为NoSuchFileException是其子类。
连接任何两个path可以使用resolve方法。简而言之,针对任何path可以调用resolve方法,传入部分path作为参数,则部分参数会追加至原path:
@Test
public void givenTwoPaths_whenJoinsAndResolves_thenCorrect() {
Path p = Paths.get("/baeldung/articles");
Path p2 = p.resolve("java");
assertEquals("\\baeldung\\articles\\java", p2.toString());
}
然而,当path字符串船只resolve方法不是部分path,而是绝对path,则传入path会直接返回:
@Test
public void givenAbsolutePath_whenResolutionRetainsIt_thenCorrect() {
Path p = Paths.get("/baeldung/articles");
Path p2 = p.resolve("C:\\baeldung\\articles\java");
assertEquals("C:\\baeldung\\articles\\java", p2.toString());
}
同样对任何参数包含根元素也会直接返回。前面“java”没有根元素,而“/java”有根元素,因此当你传入根元素时,则会直接返回:
@Test
public void givenPathWithRoot_whenResolutionRetainsIt_thenCorrect2() {
Path p = Paths.get("/baeldung/articles");
Path p2 = p.resolve("/java");
assertEquals("\\java", p2.toString());
}
相对路径表示在两个已知路径之间创建一个直接路径。举例,如果有一个目录/baeldung,其里面有两个其他目录:/baeldung/authors 和 /baeldung/articles 。
articles路径相对于authors可以描述为 “向上移动一层然后进入articles目录”,即 …/articles 。
@Test
public void givenSiblingPaths_whenCreatesPathToOther_thenCorrect() {
Path p1 = Paths.get("/base/articles");
Path p2 = Paths.get("/base/authors");
Path p1_rel_p2 = p1.relativize(p2);
Path p2_rel_p1 = p2.relativize(p1);
assertEquals("..\\authors", p1_rel_p2.toString());
assertEquals("..\\articles", p2_rel_p1.toString());
}
假设我们移动articles目录至authors目录,这样他们不再是兄弟关系。下面相对操作涉及在base和articles之间创建path,反之一样:
@Test
public void givenNonSiblingPaths_whenCreatesPathToOther_thenCorrect() {
Path p1 = Paths.get("/base");
Path p2 = Paths.get("/base/authors/articles");
Path p1_rel_p2 = p1.relativize(p2);
Path p2_rel_p1 = p2.relativize(p1);
assertEquals("authors\\articles", p1_rel_p2.toString());
assertEquals("..\\..", p2_rel_p1.toString());
}
Path类直观地实现了equals方法,使我们可以比较两条路径的相等性:
@Test
public void givenTwoPaths_whenTestsEquality_thenCorrect() {
Path p1 = Paths.get("/baeldung/articles");
Path p2 = Paths.get("/baeldung/articles");
Path p3 = Paths.get("/baeldung/authors");
assertTrue(p1.equals(p2));
assertFalse(p1.equals(p3));
}
也可以比较是否以特定字符串开头:
@Test
public void givenPath_whenInspectsStart_thenCorrect() {
Path p1 = Paths.get("/baeldung/articles");
assertTrue(p1.startsWith("/baeldung"));
}
或以特定字符串结尾:
@Test
public void givenPath_whenInspectsEnd_thenCorrect() {
Path p1 = Paths.get("/baeldung/articles");
assertTrue(p1.endsWith("articles"));
}
本文我们展示了NIO2中文件系统的Path对象api,通过示例说明应用场景及功能。