# 文件操作

java.nio.file 包中的 [Files](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html) 类是另一个主要入口点。

该类提供了一组丰富的静态方法,用于读取,写入和操作文件和目录。这些 Files 方法适用于 Path 对象的实例。在进行其余部分之前,您应该熟悉以下常见概念:

  • 释放系统资源
  • 捕捉异常
  • 可变参数
  • 原子操作
  • 方法链
  • What Is a Glob?
  • 链接感知

# 释放系统资源

此 API 中使用的许多资源(如流或通道)实现或扩展了 java.io.Closeable 接口。 Closeable 是可以关闭的数据源或目标。调用 close 方法可释放对象保存的资源(如打开文件)。 忽略关闭资源可能会对应用程序的性能产生负面影响。在下一节 try-with-resources 语句中为您处理此步骤。

# 捕捉异常

使用文件 I / O,意外情况:一个文件存在(或不存在),该程序无法访问该文件系统, 默认的文件系统实现不支持一个特定的功能, 等等。可能会遇到许多错误。

访问文件系统的所有方法都可以抛出 IOException。最好的做法是通过 JDK7 引入的 try-with-resources 语句来捕获这些异常。 使用 try-with-resources 语句,编译器会自动生成关闭的代码。帮你自动释放资源。

Charset charset = Charset.forName("US-ASCII");
String s = ...;
try (BufferedWriter writer = Files.newBufferedWriter(file, charset)) {
    writer.write(s, 0, s.length());
} catch (IOException x) {
    System.err.format("IOException: %s%n", x);
}
1
2
3
4
5
6
7

有关更多信息,请参阅 try-with-resources 语句。

或者,您可以将文件 I / O 方法嵌入到 try 块中,然后捕获块中的任何异常 catch。如果你的代码打开了任何流或通道, 你应该在一个 finally 块中关闭它们。前面的例子看起来像以下使用 try-catch-finally 方法:

Charset charset = Charset.forName("US-ASCII");
String s = ...;
BufferedWriter writer = null;
try {
    writer = Files.newBufferedWriter(file, charset);
    writer.write(s, 0, s.length());
} catch (IOException x) {
    System.err.format("IOException: %s%n", x);
} finally {
    if (writer != null) writer.close();
}
1
2
3
4
5
6
7
8
9
10
11

有关详细信息,请参阅“ 捕获和处理异常”。

除此之外 IOException 还有许多具体的例外 FileSystemException。 这个类有一些有用的方法返回涉及的文件 (getFile), 详细的消息字符串 (getMessage), 文件系统操作失败的原因 (getReason) 和所涉及的“其他”文件(getOtherFile 如果有的话 )。

try (...) {
    ...    
} catch (NoSuchFileException x) {
    System.err.format("%s does not exist\n", x.getFile());
}
1
2
3
4
5

为了清楚起见,本课程中的文件 I / O 示例可能不会显示异常处理,但是您的代码应该总是包含它。

# 可变参数

Files 当指定标志时,几种方法接受任意数量的参数。例如,在以下方法签名中,CopyOption 参数之后的椭圆符号表示该方法接受可变数量的参数,或者通常称为 varargs:

Path Files.move(Path, Path, CopyOption...)
1

当一个方法接受一个 varargs 参数时,你可以传递一个逗号分隔的值列表或数组(CopyOption[])的值。

在该 move 示例中,可以如下调用该方法:

import static java.nio.file.StandardCopyOption.*;

Path source = ...;
Path target = ...;
Files.move(source,
           target,
           REPLACE_EXISTING,
           ATOMIC_MOVE);
1
2
3
4
5
6
7
8

有关 varargs 语法的更多信息,请参阅 可变参数

# 原子操作

几种 Files 方法,例如 move,可以在某些文件系统中以原子方式执行某些操作。

一个 原子文件操作 是不能被中断或“部分”执行的操作。执行整个操作或操作失败。 当您有多个进程在文件系统的相同区域上运行时,这很重要,您需要确保每个进程访问完整的文件。

# 方法链

许多文件 I / O 方法支持 方法链接 的概念(链式编程)。

您首先调用返回对象的方法。然后立即调用该对象的方法,该方法返回另一个对象,依此类推。许多 I / O 示例使用以下技术:

String value = Charset.defaultCharset().decode(buf).toString();
UserPrincipal group =
    file.getFileSystem().getUserPrincipalLookupService().
         lookupPrincipalByName("me");
1
2
3
4

# 什么是 glob ?

Files 类中的两个方法接受一个 glob 参数,但是什么是 glob?

您可以使用 glob 语法来指定模式匹配行为。

glob 模式被指定为字符串,并与其他字符串匹配,例如目录或文件名。Glob 语法遵循几个简单的规则:

  • 一个星号 “*” 匹配任意数量的字符(包括无)。

  • 两个星号 “**”,类似 “*” 但跨越目录边界。这种语法通常用于匹配完整的路径。

  • 一个问号 “?”,恰好匹配一个字符

  • 大括号指定子模式的集合。例如:

    • {sun,moon,stars} 匹配 “sun”,“moon” 或 “stars”。
    • {temp*,tmp*} 匹配以 “temp” 或 “tmp” 开头的所有字符串。
  • 方括号传送一组单个字符,或者使用连字符(-)时,会显示一系列字符。例如:

    • [aeiou] 匹配任何小写元音。
    • [0-9] 匹配任何数字。
    • [A-Z] 匹配任何大写字母。
    • [a-z,A-Z] 匹配任何大写或小写字母。
  • 匹配,*,?,可以使用 \ 来转义。

以下是 glob 语法的一些示例:

  • *.html – 匹配以 .html 结尾的所有字符串
  • ??? – 匹配三个字母或数字的所有字符串
  • *[0-9]* – 匹配包含数值的所有字符串
  • *.{htm,html,pdf} – 匹配以 .htm, .html or .pdf 结尾的字符串
  • a?*.java – 匹配任何以 a 开头的字符串,后至少跟一字母或数字,以 .java 结尾
  • {foo*,*[0-9]*} – 匹配以 foo 开头的任何字符串或包含数值的任何字符串

glob 语法功能强大且易于使用。但是,如果您的需求不足,您还可以使用正则表达式。有关更多信息,请参阅 正则表达式课程

有关 glob sytnax 的更多信息,请参阅 API 规范 FileSystem 类中的getPathMatcher 方法 。

# 链接感知

该 Files 课程是“链接感知”。每个 Files 方法或者检测到遇到符号链接时该怎么做,或者提供一个选项,使您能够在遇到符号链接时配置该行为。