1. 概述

本文将深入探讨 Java 7 引入的 NIO2(New I/O 2)中的 Path API。作为 Java 7 最重要的新特性之一,Path API 与传统 File API 共同构成了全新的文件系统操作体系,为开发者提供了更强大、更灵活的文件路径处理能力。

2. 环境准备

NIO2 的核心功能集中在 java.nio.file 包中。使用 Path API 只需导入该包:

import java.nio.file.*;

为确保代码在不同环境中的兼容性,我们获取用户主目录作为基准路径:

private static String HOME = System.getProperty("user.home");

Paths 类是所有路径操作的入口点,用于创建和操作文件/目录路径。重要提示:路径操作本质上是语法层面的,不会影响底层文件系统,文件系统也不会影响操作的成败。这意味着操作不存在的路径不会导致失败。

3. 路径操作基础

Path 类是文件系统路径的程序化表示,包含文件名和目录列表,用于检查、定位和操作文件。java.nio.file.Paths(注意复数形式)是创建 Path 对象的标准工具类,提供两种静态方法:

从路径字符串创建:

Path path = Paths.get("path string");

路径分隔符(/ 或 \)会被自动适配底层文件系统

从 URI 对象创建:

Path path = Paths.get(URI object);

4. 创建路径对象

通过路径字符串创建 Path 对象:

@Test
public void givenPathString_whenCreatesPathObject_thenCorrect() {
    Path p = Paths.get("/articles/baeldung");
 
    assertEquals("\\articles\\baeldung", p.toString());
}

get() 方法支持可变参数,自动拼接路径部分:

@Test
public void givenPathParts_whenCreatesPathObject_thenCorrect() {
    Path p = Paths.get("/articles", "baeldung");
    
    assertEquals("\\articles\\baeldung", p.toString());
}

✅ 无需手动添加分隔符,API 会自动处理

5. 获取路径信息

Path 可视为名称元素的序列。例如路径 E:\baeldung\articles\java 包含三个元素:

  • 索引 0:baeldung(最高级目录)
  • 索引 1:articles
  • 索引 2:java(最低级元素,称为文件名

获取文件名:

@Test
public void givenPath_whenRetrievesFileName_thenCorrect() {
    Path p = Paths.get("/articles/baeldung/logs");

    Path fileName = p.getFileName();
 
    assertEquals("logs", fileName.toString());
}

按索引获取元素:

@Test
public void givenPath_whenRetrievesNameByIndex_thenCorrect() {
    Path p = Paths.get("/articles/baeldung/logs");
    Path name0 = getName(0);
    Path name1 = getName(1);
    Path name2 = 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());
}

获取父路径:

@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);
}

获取根路径:

@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());
}

6. 规范化路径

文件系统常用 . 表示当前目录,.. 表示父目录。路径可能包含冗余信息,例如:

/baeldung/./articles
/baeldung/authors/../articles
/baeldung/articles

以上路径都指向相同位置 /baeldung/articles。规范化操作可消除这些冗余:

@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());
}

7. 路径转换

转换为浏览器可访问的 URI

@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());
}

转换为绝对路径

@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());
}

⚠️ 若路径已是绝对路径,则直接返回原路径:

@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() 会解析符号链接并验证路径存在性:

@Test
public void givenExistingPath_whenGetsRealPathToFile_thenCorrect() {
    Path p = Paths.get(HOME);

    Path realPath = p.toRealPath();
 
    assertEquals(HOME, realPath.toString());
}

❌ 若路径不存在,抛出 NoSuchFileException

@Test(expected = NoSuchFileException.class)
public void givenInExistentPath_whenFailsToConvert_thenCorrect() {
    Path p = Paths.get("E:\\home\\baeldung\\articles.html");
    
    p.toRealPath();
}

8. 合并路径

使用 resolve() 合并路径。当传入相对路径时,会追加到原路径:

@Test
public void givenTwoPaths_whenJoinsAndResolves_thenCorrect() {
    Path p = Paths.get("/baeldung/articles");

    Path p2 = p.resolve("java");
 
    assertEquals("\\baeldung\\articles\\java", p2.toString());
}

⚠️ 传入绝对路径时,直接返回传入路径:

@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());
}

⚠️ 传入带根元素的路径时,同样直接返回:

@Test
public void givenPathWithRoot_whenResolutionRetainsIt_thenCorrect2() {
    Path p = Paths.get("/baeldung/articles");

    Path p2 = p.resolve("/java");
 
    assertEquals("\\java", p2.toString());
}

9. 相对化路径

relativize() 创建两个路径间的直接路径。例如同级目录:

@Test
public void givenSiblingPaths_whenCreatesPathToOther_thenCorrect() {
    Path p1 = Paths.get("articles");
    Path p2 = Paths.get("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());
}

非同级目录示例:

@Test
public void givenNonSiblingPaths_whenCreatesPathToOther_thenCorrect() {
    Path p1 = Paths.get("/baeldung");
    Path p2 = Paths.get("/baeldung/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());
}

10. 比较路径

路径相等性

@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"));
}

11. 总结

本文系统介绍了 Java 7 NIO2 中的 Path API,涵盖路径创建、信息检索、规范化、转换、合并、相对化和比较等核心操作。这些 API 显著提升了文件路径处理的灵活性和可靠性,是现代 Java 开发中不可或缺的工具。

完整代码示例可在 GitHub 项目 中获取。


原始标题:Java NIO2 Path API