Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
Files 类是 java.nio.file 包的另一个主要入口点。该类提供了一组丰富的静态方法,用于读取、写入和操作文件和目录。Files 方法适用于 Path 对象的实例。在继续讨论其余部分之前,你应该熟悉以下常见概念:
此 API 中使用的许多资源(例如流或通道)实现或继承 java.io.Closeable 接口。Closeable 资源的要求是必须调用 close 方法以在不再需要时释放资源。忽略关闭资源可能会对应用程序的性能产生负面影响。try-with-resources 语句(将在下一节中介绍)为你处理此步骤。
对于文件 I/O,意外情况是生活中的事实:文件在预期的时候存在(或不存在),程序无法访问文件系统,默认文件系统实现不支持特定功能, 等等。可能遇到许多错误。
访问文件系统的所有方法都可以抛出 IOException。最佳实践是通过将这些方法嵌入 Java SE 7 发行版中引入的 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);
}
有关更多信息,请参阅 The try-with-resources Statement。
或者,你可以将文件 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();
}
有关更多信息,请参阅 Catching and Handling Exceptions。
除了 IOException 之外,还有许多特定的异常继承 FileSystemException。这个类有一些有用的方法可以返回涉及的文件 (getFile),详细的消息字符串 (getMessage),文件系统操作失败的原因 (getReason),以及涉及的“其他”文件,如果有的话 (getOtherFile)。
以下代码段显示了如何使用 getFile 方法:
try (...) {
...
} catch (NoSuchFileException x) {
System.err.format("%s does not exist\n", x.getFile());
}
为清楚起见,本课程中的文件 I/O 示例可能不会显示异常处理,但你的代码应始终包含它。
指定标志时,有几个 Files 方法接受任意数量的参数。例如,在以下方法签名中,CopyOption 参数后面的省略号表示该方法接受可变数量的参数,它们通常被称为 varargs:
Path Files.move(Path, Path, CopyOption...)
当方法接受 varargs 参数时,你可以传递一个以逗号分隔的值列表或值的数组(CopyOption[])。
在 move 示例中,可以按如下方式调用该方法:
import static java.nio.file.StandardCopyOption.*;
Path source = ...;
Path target = ...;
Files.move(source,
target,
REPLACE_EXISTING,
ATOMIC_MOVE);
有关 varargs 语法的更多信息,请参阅 Arbitrary Number of Arguments。
一些 Files 方法(例如 move)可以在某些文件系统中以原子方式执行某些操作。
atomic file operation (原子文件操作) 是不能被中断或“部分”执行的操作。要么执行整个操作,要么操作失败。当你在文件系统的同一区域上运行多个进程时,这很重要,并且你需要保证每个进程都访问一个完整的文件。
许多文件 I/O 方法支持 method chaining (方法链) 的概念。
你首先调用返回对象的方法。然后立即在 that (那个) 对象上执行方法,该方法返回另一个对象,依此类推。许多 I/O 示例使用以下技术:
String value = Charset.defaultCharset().decode(buf).toString();
UserPrincipal group =
file.getFileSystem().getUserPrincipalLookupService().
lookupPrincipalByName("me");
此技术生成紧凑的代码,使你可以避免声明不需要的临时变量。
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 或 .pdf 结尾的任何字符串a?*.java - 匹配以 a 开头,后跟至少一个字母或数字,以 .java 结尾的任何字符串{foo*,*[0-9]*} - 匹配以 foo 开头的任何字符串或包含数值的任何字符串"*")中,使用反斜杠(\*),或使用命令行支持的任何转义机制。
glob 语法功能强大且易于使用。但是,如果它不足以满足你的需求,你还可以使用正则表达式。有关更多信息,请参阅 Regular Expressions 课程。
有关 glob 语法的更多信息,请参阅 FileSystem 类中 getPathMatcher 方法的 API 规范。
Files 类是“链接感知”。每个 Files 方法都会检测遇到符号链接时要执行的操作,或者它提供了一个选项,使你可以在遇到符号链接时配置行为。