Java NIO2 Path API

Java NIO2 Path API

本文我们学习如何使用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规范化

很多文件系统使用".“表示当前目录,使用”…"表示父目录。你可能会遇到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转成可选的表现格式。为了转换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

连接任何两个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());
}

相对path

相对路径表示在两个已知路径之间创建一个直接路径。举例,如果有一个目录/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

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,通过示例说明应用场景及功能。

你可能感兴趣的:(Java NIO2 Path API)