1. 概述
在Java中, Path 和 File 是负责文件I/O操作的类。它们执行相同的功能但属于不同的包。
在本教程中,我们将讨论这两个类之间的差异。我们将从快速回顾课程开始。然后,我们将讨论一些遗留的缺点。最后,我们将学习如何在两个 API 之间迁移功能。
2. java.io.File 类
从第一个版本开始,Java 就提供了自己的 java.io 包,其中几乎包含我们执行输入和输出操作可能需要的所有类。 File 类是 文件和目录路径名的抽象表示 :
File file = new File("baeldung/tutorial.txt");
File 类的实例是不可变的——一旦创建,该对象表示的抽象路径名就永远不会改变。
3. java.nio.file.Path 类
Path 类是NIO2更新的一部分,该更新随版本 7 一起出现在 Java 中。它提供了 一个全新的 API 来处理 I/O 。此外,与旧版 File 类一样, Path 还创建 一个可用于在文件系统中定位文件的对象 。
同样,它可以执行 File 类可以完成的 所有操作 :
Path path = Paths.get("baeldung/tutorial.txt");
我们没有像使用 File API 那样使用构造函数,而是使用静态 java.nio.file.Paths.get() 方法创建 Path 实例。
4. 文件 类的缺点
在简短回顾这两个类之后,现在让我们讨论这两个 API 并回答以下问题:如果它们提供相同的功能, 为什么 Oracle 决定引入新的 API,以及我应该使用哪一个?
众所周知, java .io 包是随 Java JDK 的第一个版本一起提供的,允许我们执行 I/O 操作。从那时起,许多开发人员报告了它的许多缺点、功能缺失以及某些功能的问题。
4.1.错误处理
最常见的问题是错误处理能力差。许多方法 不会告诉我们所遇到问题的任何细节 ,甚至根本不会抛出异常。
假设我们有一个删除文件的简单程序:
File file = new File("baeldung/tutorial.txt");
boolean result = file.delete();
这段代码编译并成功运行,没有任何错误。当然,我们有一个包含 错误 值 的结果 标志,但我们不知道此失败的原因。该文件可能不存在,或者程序可能没有删除它的权限。
我们现在可以使用更新的 NIO2 API 重写相同的功能:
Path path = Paths.get("baeldung/tutorial.txt");
Files.delete(path);
现在,编译器要求我们处理 IOException 。此外,抛出的异常还包含有关其失败的详细信息,例如,该文件是否不存在。
4.2.元数据支持
java.io 包中的 File 类对元数据的支持很差,这会导致跨平台的I/O操作需要有关文件的元信息的问题。
元数据还可以包括权限、文件所有者和安全属性。因此, File 类根本 不支持符号链接 ,并且 rename() 方法在不同平台上的工作方式也不一致 。
4.3.方法扩展和性能
由于 File 类的方法不可扩展,因此还存在性能问题。它会导致某些包含大量文件的目录出现问题。列出目录的内容 可能会导致挂起,从而导致内存资源问题 。最后,它可能导致拒绝服务。
由于其中一些缺点,Oracle 开发了改进的 NIO2 API。开发人员 应该尽可能使用这个新的 java.nio 包而不是遗留类来启动新项目 。
5. 映射功能
为了修复 java.io 包中的一些缺陷,Oracle准备了自己的缺陷总结,帮助开发人员在API之间进行迁移。
NIO2 包提供了所有遗留功能,包括针对上述缺点的改进。由于大量应用程序可能仍在使用此旧 API,Oracle 目前 不打算在未来版本中弃用或删除旧 API 。
在新的 API 中,我们使用 java.nio.file.Files 类中的 静态方法,而不是实例方法 。现在让我们快速比较一下这些 API。
5.1. 文件 和 路径 实例
当然,主要区别在于包和类名:
java.io.File file = new java.io.File("baeldung/tutorial.txt");
java.nio.file.Path path = java.nio.file.Paths.get("baeldung/tutorial.txt");
这里,我们通过构造函数构建一个 File 对象,同时使用静态方法获取 Path 。我们还可以使用多个参数解析复杂路径:
File file = new File("baeldung", "tutorial.txt");
Path path = Paths.get("baeldung", "tutorial.txt");
并且,我们可以通过链接 resolve() 方法来达到相同的结果:
Path path2 = Paths.get("baeldung").resolve("tutorial.txt");
此外,我们可以使用 toPath() 和 toFile() 方法在 API 之间转换对象:
Path pathFromFile = file.toPath();
File fileFromPath = path.toFile();
5.2.管理文件和目录
这两个 API 都提供了管理文件和目录的方法。我们将使用之前创建的实例对象来演示这一点。
要创建文件 ,我们可以使用 createNewFile() 和 Files.createFile() 方法:
boolean result = file.createNewFile();
Path newPath = Files.createFile(path);
要创建目录 ,我们需要使用 mkdir() 或 Files.createDirectory() :
boolean result = file.mkdir();
File newPath = Files.createDirectory(path);
这些方法还有其他变体,可以通过 mkdirs() 和 Files.createDirectories() 方法 包含所有不存在的子目录 :
boolean result = file.mkdirs();
File newPath = Files.createDirectories(path);
当我们想要 重命名或移动文件 时,我们需要创建另一个实例对象并使用 renameTo() 或 Files.move() :
boolean result = file.renameTo(new File("baeldung/tutorial2.txt"));
Path newPath = Files.move(path, Paths.get("baeldung/tutorial2.txt"));
要执行删除操作 ,我们使用 delete() 或 Files.delete() :
boolean result = file.delete();
Files.delete(Paths.get(path));
请注意,如果出现任何错误,旧方法会返回一个结果设置为 false 的标志。 NIO2 方法返回一个新的 Path 实例,但删除操作除外,当发生错误时会抛出 IOException 。
5.3.读取元数据
我们还可以获得文件的一些基本信息,例如权限或类型。和以前一样,我们需要一个实例对象:
// java.io API
boolean fileExists = file.exists();
boolean fileIsFile = file.isFile();
boolean fileIsDir = file.isDirectory();
boolean fileReadable = file.canRead();
boolean fileWritable = file.canWrite();
boolean fileExecutable = file.canExecute();
boolean fileHidden = file.isHidden();
// java.nio API
boolean pathExists = Files.exists(path);
boolean pathIsFile = Files.isRegularFile(path);
boolean pathIsDir = Files.isDirectory(path);
boolean pathReadable = Files.isReadable(path);
boolean pathWritable = Files.isWritable(path);
boolean pathExecutable = Files.isExecutable(path);
boolean pathHidden = Files.isHidden(path);
5.4.路径名方法
最后,让我们快速浏览一下 File 类中用于获取文件系统路径的方法。请注意,与前面的示例不同,它们中的大多数都是直接在对象实例上执行的。
要获取绝对路径或规范路径 ,我们可以使用:
// java.io API
String absolutePathStr = file.getAbsolutePath();
String canonicalPathStr = file.getCanonicalPath();
// java.nio API
Path absolutePath = path.toAbsolutePath();
Path canonicalPath = path.toRealPath().normalize();
虽然 Path 对象是不可变的,但它返回一个新实例。此外,NIO2 API 具有 toRealPath() 和 normalize() 方法,我们可以使用它们来删除冗余。
可以使用 toUri() 方法 转换为 URI :
URI fileUri = file.toURI();
URI pathUri = path.toUri();
另外,我们可以 列出目录内容 :
// java.io API
String[] list = file.list();
File[] files = file.listFiles();
// java.nio API
DirectoryStream<Path> paths = Files.newDirectoryStream(path);
NIO2 API 返回自己的 DirectoryStream 对象,该对象实现了 Iterable 接口。
六,结论
从 Java 7 开始,开发人员现在可以在两个 API 之间进行选择来处理文件。在本文中,我们讨论了与 java.io.File 类相关的一些不同的缺点和问题。
为了解决这些问题,Oracle 决定提供 NIO 包,它带来了相同的功能并进行了大量改进 。
然后,我们回顾了这两个 API。通过示例,我们学习了如何在它们之间进行迁移。我们还了解到, java.io.File 现在被视为遗留文件,不建议用于新项目 。但是,没有计划弃用和删除它。
与往常一样,本文中的所有代码片段都可以在 GitHub 上获取。