本文重点介绍应用程序程序员可能遇到的任务,特别是在 Web 应用程序中,例如:
- 读写文本文件
- 从网络上读取文本、图像、JSON
- 访问目录中的文件
- 读取 ZIP 文件
- 创建临时文件或目录
本文重点介绍 Java 8 以来的 API 改进。特别是:
- 从 Java 18 开始,UTF-8 成为 I/O 的默认设置(自JEP 400:默认为 UTF-8以来)
- 该类java.nio.file.Files首次出现在 Java 7 中,并在 Java 8、11 和 12 中添加了一些有用的方法
- java.io.InputStream在 Java 9、11 和 12 中获得了有用的方法
- 尽管java.io.File和类在网络搜索和 AI 聊天中频繁出现,但它们现在已经彻底过时了。java.io.BufferedReader
1、读取文本文件
您可以像这样将文本文件读入字符串:
String content = Files.readString(path); |
这里,path是 的一个实例java.nio.Path,获取方式如下:
var path = Path.of("/usr/share/dict/words"); |
在 Java 18 之前,强烈建议您在任何读取或写入字符串的文件操作中指定字符编码。如今,迄今为止最常见的字符编码是 UTF-8,但为了向后兼容,Java 使用了“平台编码”,这可能是 Windows 上的遗留编码。为了确保可移植性,文本 I/O 操作需要参数StandardCharsets.UTF_8。这不再是必要的。
如果您希望将文件作为一系列行,请调用
List<String> lines = Files.readAllLines(path); |
如果文件很大,请按如下方式惰性处理各行Stream
try (Stream<String> lines = Files.lines(path)) { |
Files.lines如果您可以使用流操作(例如map、 )自然地处理行,也可以使用filter。请注意,返回的流Files.lines需要关闭。为确保发生这种情况,请使用try-with-resources语句,如前面的代码片段所示。
不再有充分的理由使用 该readLine方法java.io.BufferedReader。
要将输入拆分为行以外的内容,请使用java.util.Scanner。例如,以下是如何读取由非字母分隔的单词:
Stream<String> tokens = new Scanner(path).useDelimiter("\\PL+").tokens(); |
该类Scanner还具有读取数字的方法,但通常将输入读取为每行一个字符串或单个字符串,然后进行解析更为简单。
解析文本文件中的数字时要小心,因为它们的格式可能与语言环境有关。例如,100.000在美国语言环境中输入的是 100.0,但在德国语言环境中输入的是 100000.0。用于java.text.NumberFormat特定于语言环境的解析。或者,您也可以使用Integer.parseInt/ Double.parseDouble。
2、写入文本文件
您只需一次调用即可将字符串写入文本文件:
String content = . . .; |
如果您有一个行列表而不是单个字符串,请使用:
List<String> lines = . . .; |
如需更通用的输出,PrintWriter如果您想使用该printf方法,请使用:
var writer = new PrintWriter(path.toFile()); |
请注意,printf是特定于语言环境的。写数字时,请务必使用适当的格式。不要使用printf,而应考虑使用java.text.NumberFormat或Integer.toString/ Double.toString。
奇怪的是,从 Java 21 开始,没有带参数PrintWriter的构造函数Path。
如果不使用printf,您可以使用BufferedWriter类并使用方法编写字符串write。
var writer = Files.newBufferedWriter(path); |
writer完成后请记得关闭。
3、从输入流读取
也许使用流的最常见原因是从网站上读取一些内容。
如果需要设置请求标头或读取响应标头,请使用HttpClient:
HttpClient client = HttpClient.newBuilder().build(); |
如果你想要的只是数据,那么这种方法就太过分了。相反,可以使用:
InputStream in = new URI("https://horstmann.com/index.html").toURL().openStream(); |
然后将数据读入字节数组并选择性地将其转换为字符串:
byte[] bytes = in.readAllBytes(); |
或者将数据传输到输出流:
OutputStream out = Files.newOutputStream(path); |
请注意,如果您只是想读取输入流的所有字节,则不需要循环。
但你真的需要输入流吗?许多 API 都提供了从文件或 URL 读取的选项。
你最喜欢的 JSON 库可能具有从文件或 URL 读取的方法。例如,使用Jackson jr:
URL url = new URI("https://dog.ceo/api/breeds/image/random").toURL(); |
以下是如何从前面的调用中读取狗的图像:
URL url = new URI(result.get("message").toString()).toURL(); |
这比将输入流传递给方法更好read,因为库可以使用来自 URL 的附加信息来确定图像类型。
文件 API
该java.nio.file.Files课程提供了一套全面的文件操作,例如创建、复制、移动和删除文件和目录。文件系统基础教程提供了详尽的描述。在本节中,我将重点介绍一些常见任务。
4、遍历目录和子目录中的条目
大多数情况下,您可以使用以下两种方法之一。Files.list方法访问目录中的所有条目(文件、子目录、符号链接)。
try (Stream<Path> entries = Files.list(pathToDirectory)) { |
使用try-with-resources语句确保跟踪迭代的流对象将被关闭。
如果你还想访问后代目录的条目,请使用该方法Files.walk
Stream<Path> entries = Files.walk(pathToDirectory); |
然后只需使用流方法来定位您感兴趣的条目并收集结果:
try (Stream<Path> entries = Files.walk(pathToDirectory)) { |
以下是遍历目录条目的其他方法:
- Files.walk 的重载版本可让您限制遍历树的深度。
- 两个 Files.walkFileTree 方法可在首次和最后一次访问目录时通知 FileVisitor,从而对遍历过程进行更多控制。 这偶尔会很有用,特别是在清空和删除目录树时。 有关详情,请参阅教程 "走动文件树"。
- Files.find 方法就像 Files.walk,只是提供了一个过滤器来检查每个路径及其 BasicFileAttributes。
- 两个 Files.newDirectoryStream(Path) 方法产生 DirectoryStream 实例,可用于增强 for 循环。
- 传统的 File.list 或 File.listFiles 方法会返回文件名或文件对象,与使用 Files.list 相比没有优势。 这些方法现已过时。
5、使用 ZIP 文件
自 Java 1.1 以来,ZipInputStream和ZipOutputStream类提供了用于处理 ZIP 文件的 API。但该 API 有点笨重。Java 8 引入了一个更好的ZIP 文件系统:
try (FileSystem fs = FileSystems.newFileSystem(pathToZipFile)) { |
try-with-resources语句确保close在 ZIP 文件操作之后调用该方法。该方法会更新 ZIP 文件以反映文件系统中的任何更改。
然后,您就可以使用该类的方法Files。这里我们获取了 ZIP 文件中所有文件的列表:
try (Stream<Path> entries = Files.walk(fs.getPath("/"))) { |
要读取文件内容,只需使用Files.readString或Files.readAllBytes:
String contents = Files.readString(fs.getPath("/LICENSE")); |
您可以使用 删除文件Files.delete。要添加或替换文件,只需使用Files.writeString或Files.write。
6、创建临时文件和目录
我经常需要收集用户输入、生成文件并运行外部进程。然后我会使用临时文件(下次重启后会消失)或临时目录(进程完成后会删除)。
Files.createTempFile为此,我使用了这两种方法Files.createTempDirectory。
Path filePath = Files.createTempFile("myapp", ".txt"); |
这将在合适的位置(/tmp在 Linux 中)创建一个临时文件或目录,并带有给定的前缀和文件后缀。
结论
网络搜索和 AI 聊天可能会为常见的 I/O 操作推荐不必要的复杂代码。通常有更好的替代方案:
- 您不需要循环来读取或写入字符串或字节数组。
- 您甚至可能不需要流、reader 或writer。
- 熟悉Files创建、复制、移动和删除文件和目录的方法。
- 使用Files.list或Files.walk遍历目录条目。
- 使用 ZIP 文件系统处理 ZIP 文件。
- 远离遗留File类。