概述

在早期的 Java 版本中,文件 IO 操作功能一直相对较弱,主要存在以下问题:

  1. 缺乏对现代文件系统的支持:只提供的基础的文件操作,不支持很多现代的文件系统
  2. API 不够直观:文件操作的 API 设计相对较为复杂和冗长,使用体验感很差
  3. 对于大文件处理和并发性能不够:简单的 I/O 模型,没有充分利用现代硬件的性能优势,而且还有很多同步的问题

但 Java 在后期版本中引入了 java.nio.file 库来提高 Java 对文件操作的能力。还增加的流的功能,似乎使得文件变成更好用了。所以本章,我们就来主要介绍 java.nio.file 中常用的类和模块,大致如下:

  1. Path 路径:Paths 模块和 Path 工具类介绍
  2. Files 文件:File 和 FileSystems 工具类介绍
  3. 文件管理服务:WatchService 、PathMatcher 等等文件服务

Path 路径

java.nio.file.Pathsjava.nio.file.Path 类在 Java NIO 文件 I/O 框架中用于处理文件系统路径。以下是对它们的简单介绍:

  • Paths 模块:Paths 模块提供了一些静态方法来创建 Path 对象,Path 对象表示文件系统中的路径。例如,可以使用 Paths.get() 方法创建一个 Path 对象,这个对象表示一个文件路径。
  • Path 类:Path 类代表一个文件系统中的路径,它提供了一系列的方法来操作文件路径。例如,可以使用 Path.toAbsolutePath() 方法获取一个绝对路径,或者使用 Path.getParent() 方法获取路径的父路径。

关于跨平台:Path 对象可以工作在不同操作系统的不同文件系统之上,它帮我们屏蔽了操作系统之间的差异

以下是一些简单使用场景示例:

import java.nio.file.Path;
import java.nio.file.Paths; public class PathExample { public static void main(String[] args) {
// 创建一个绝对路径
Path absolutePath = Paths.get("C:\\Users\\phoenix\\file.txt"); // 这里传入 "example\\file.txt" 创建的相对路径
System.out.println("Absolute path: " + absolutePath);
// 获取父路径
System.out.println("Parent path: " + absolutePath.getParent());
// 获取文件名
System.out.println("File name: " + absolutePath.getFileName());
// 获取根路径
System.out.println("Root path: " + absolutePath.getRoot());
// 合并路径
Path resolvePath = Paths.get("C:\\Users\\phoenix").resolve("file.txt");
System.out.println("Merged path:" + resolvePath);
}
}

输出结果:

Absolute path: C:\Users\phoenix\file.txt
Parent path: C:\Users\phoenix
File name: file.txt
Root path: C:\
Merged path:C:\Users\phoenix\file.txt

从这里你不仅可以看出关于 PathsPath 类对于文件路径的一些操作方法的使用,还能看得出我使用的是 Windows 操作系统。还有更多的用法可以查看官方的 API 文档,这里就不过多赘述了。

Files 文件

java.nio.file.Files 类是 Java NIO 文件包中的一个实用工具类,它提供了一系列静态方法,可以让你方便地执行文件系统中的各种操作,例如文件的创建、删除、复制、移动、读取和写入等。例如,可以使用 Files.exists() 方法检查一个文件是否存在,或者使用 Files.createDirectory() 方法创建一个新目录。

以下是一些简单使用场景示例:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Arrays;
import java.util.List; public class PathExample { public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
// 1:检查文件是否存在
boolean exists = Files.exists(path);
System.out.println("File exists: " + exists);
if (!exists) {
// 2:不存在则创建文件
Files.createFile(path);
}
// 3:复制一个文件
Path target = Paths.get("example2.txt");
Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
// 4:创建目录
Path newDirectory = Paths.get("example");
Files.createDirectories(newDirectory);
// 4:移动文件:将 example2.txt 移动到 example 目录下
Files.move(target, newDirectory.resolve("example2.txt"), StandardCopyOption.REPLACE_EXISTING);
// 5:删除文件和目录
Files.delete(newDirectory.resolve("example2.txt"));
Files.delete(newDirectory); // 只能删除空目录
// 6:将字节数组写入文件
Files.write(path, "Hello World".getBytes());
// 7:将文本行序列写入文件
List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(path, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
// 8:读取文件,并且打印所有行
Files.readAllLines(path, StandardCharsets.UTF_8).forEach(System.out::println);
}
}

输出结果:

File exists: true
Line 1
Line 2
Line 3

也可以在项目根目录下查看文件:

以上代码示例展示了如何使用 Files 类进行常见的文件操作。在实际项目中,您可以根据需要组合使用这些方法来满足您的需求。

补充:

Files.delete 函数只能删除空目录,这个设计是有意为之的,因为递归地删除文件和目录可能是一个非常危险的操作,尤其是当您不小心删除了一个包含重要数据的目录时。如果您想删除一个包含子目录和文件的目录,您需要先递归地删除目录中的所有子目录和文件,然后再删除目录本身。可以借助 Files.walkFileTree 遍历文件目录,然后调用 Files.delete 即可。

FileSystems 文件系统

FileSystems 类提供了一组静态方法来访问和操作默认文件系统(通常是操作系统的本地文件系统)以及其他文件系统实现。以下是一个简单的示例:

public class FileSystemsExample {

    public static void main(String[] args) {
// 获取默认文件系统
FileSystem fileSystem = FileSystems.getDefault();
// 获取文件系统的路径分隔符
String pathSeparator = fileSystem.getSeparator();
System.out.println("Path separator: " + pathSeparator);
// 获取文件系统的根目录
for (Path root : fileSystem.getRootDirectories()) {
System.out.println("Root directory: " + root);
}
// 使用文件系统创建一个 path 路径对象
Path path = fileSystem.getPath("path", "to", "file.txt");
System.out.println(path);
// 是否只读
System.out.println("is read only ?: " + fileSystem.isReadOnly());
// 文件系统的提供者
System.out.println("provider: " + fileSystem.provider());
}
}

输出结果:

Path separator: \
Root directory: C:\
path\to\file.txt
is read only ?: false
provider: sun.nio.fs.WindowsFileSystemProvider@5b480cf9

FileSystem 工具类的方法并不多,可以参考它的 API,但通过 FileSystem 可以创建 WatchService 和 PathMatcher 子类

WatchService 文件监控

WatchService 是一个文件系统观察者,基于 FileSystem 创建,主要用于监控文件系统事件(如创建、修改、删除文件或目录)。它可以帮助我们实时地检测和处理文件系统中的变化。如果你的业务中有需要监控文件变化的场景,你可能会需要用到它,例如:

  • 文件上传
  • 实时备份
  • 热加载配置

以下是一个简单的示例:

import java.io.IOException;
import java.nio.file.*; public class WatchServiceExample { public static void main(String[] args) throws IOException, InterruptedException {
// 创建 WatchService
WatchService watchService = FileSystems.getDefault().newWatchService(); // 注册监听指定的目录
Path dir = Paths.get("C:\\Users\\phoenix");
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); while (true) {
// 获取并处理事件
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("Event: " + event.kind() + " - " + event.context());
} // 重置 key,继续监听
if (!key.reset()) {
break;
}
}
watchService.close();
}
}

启动以上程序,程序就会监控我当前系统的用户目录,当我在用户目录创建文件并且编辑,删除,程序会输出以下内容:

Event: ENTRY_CREATE - 新建 文本文档.txt
Event: ENTRY_DELETE - 新建 文本文档.txt
Event: ENTRY_CREATE - helloWorld.txt
Event: ENTRY_MODIFY - helloWorld.txt
Event: ENTRY_MODIFY - helloWorld.txt
Event: ENTRY_MODIFY - helloWorld.txt
Event: ENTRY_DELETE - helloWorld.txt

PathMatcher 文件匹配

PathMatcher 是一个文件路径匹配接口,它可以帮助我们在遍历文件系统时,根据特定规则过滤出符合条件的文件或目录。它可以使用多种匹配语法(如 glob 和 regex),使得处理文件名或目录名的模式变得更加灵活和高效。PathMatcher 的使用场景包括:

  • 文件过滤:在搜索文件时,我们可能需要根据文件名或目录名的模式来过滤结果
  • 批量操作:当我们需要对文件系统中的一组文件或目录执行批量操作时,PathMatcher 可以帮助我们找到符合特定规则的文件或目录
  • 目录监控:可以结合 WatchService 对目录监控,然后通过 PathMatcher 过滤找出我们想要文件,如:.log 文件的创建,修改等

以下是一个简单示例代码:

import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream; public class PathMatcherExample { public static void main(String[] args) throws IOException {
// 创建 PathMatcher,使用 glob 语法:匹配所有以 .tmp 结尾的文件(临时文件)
FileSystem fileSystem = FileSystems.getDefault();
PathMatcher matcher = fileSystem.getPathMatcher("glob:*.tmp");
// 在指定目录,找到匹配的文件,然后进行删除
try (Stream<Path> walk = Files.walk(Paths.get("path/to/directory"))) {
walk.filter(path -> matcher.matches(path.getFileName())).forEach(path -> {
System.out.println(path.getFileName());
try {
Files.delete(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
}

上面的示例程序是通过 PathMatcher 匹配 .tmp 结尾的临时文件,然后进行删除的示例,结合 PathMatcher 可以轻松的完成一个清理临时文件的小程序。

读文件内容

上面的示例都是操作文件和目录,这里介绍一下如何读文件的内容,为了方便演示读取文件,先在 path/to/file.txt 相对目录下创建一个示例文本:

Java is a high-level programming language.
Python is an interpreted, high-level programming language.
JavaScript is a scripting language for Web development.
C++ is a general-purpose programming language.
Rust is a systems programming language.

读文件主要用到 Files 类的两个方法:

  1. readAllLines() 方法:一次性加载,主要用于读取小到中等的文件
  2. lines() 方法:逐行读取,适用于大文件

小文件

readAllLines() 适用于读取小到中等大小的文件,因为它会将整个文件内容加载到内存中,这个方法适用于在读取文件内容后立即处理整个文件的情况。使用示例:

public class LinesExample {

    public static void main(String[] args) throws IOException {
// 读取全部文件
List<String> lines = Files.readAllLines(Paths.get("path/to/file.txt"), StandardCharsets.UTF_8); // 对文件内容进行处理
Map<String, Long> wordFrequency = lines.stream()
.flatMap(line -> Arrays.stream(line.split("\\s+")))
.map(String::toLowerCase)
.collect(Collectors.groupingBy(word -> word, Collectors.counting())); System.out.println("Word Frequency:");
wordFrequency.forEach((word, count) -> System.out.printf("%s: %d%n", word, count));
}
}

大文件

lines() 方法: 使用场景:适用于读取大型文件,因为它不会一次性将整个文件内容加载到内存中。通过使用 Java 8 的 Stream API,可以在读取文件内容时同时处理每一行,从而提高处理效率。使用示例:

public class LinesExample {

    public static void main(String[] args) throws IOException {
Path filePath = Paths.get("path/to/file.txt"); // 逐行读取,并且在内容进行处理
Stream<String> lines = Files.lines(filePath);
Map<String, Long> wordFrequency = lines
.skip(3) // 跳过前 3 行
.flatMap(line -> Arrays.stream(line.split("\\s+")))
.map(String::toLowerCase)
.collect(Collectors.groupingBy(word -> word, Collectors.counting())); System.out.println("Word Frequency:");
wordFrequency.forEach((word, count) -> System.out.printf("%s: %d%n", word, count));
lines.close();
}
}

输出结果:

Word Frequency:
rust: 1
a: 2
c++: 1
systems: 1
language.: 2
is: 2
programming: 2
general-purpose: 1

总结

在过去,java.io 包主要负责处理文件 I/O。但是它存在一些问题,例如性能不佳、API 不直观、文件元数据操作困难等。为了解决这些问题,后期的 Java 版本引入了新的 java.nio.file 库。现在 java.nio.file 已经成为处理文件 I/O 的首选库。 PathFilesFileSystem 等工具类,可以更方便快捷的访问和操作文件系统。目前大多数的开发人员普遍认为 java.nio.file 比传统的 java.io 包更直观且易于使用。虽然 java.nio.file 库已经非常成熟,但是随着操作系统和文件系统的发展,我们仍然可以期待在未来的 Java 版本中看到它的一些扩展和改进。

优雅的操作文件:java.nio.file 库介绍的更多相关文章

  1. Java7 新特性 —— java.nio.file 文件操作

    本文部分摘自 On Java 8 自 Java7 开始,Java 终于简化了文件读写的基本操作,新增了 java.nio.file 库,通过与 Java8 新增的 stream 结合可以使得文件操作变 ...

  2. java.nio.file.FileSystemException: D:\kafka_2.12-2.1.0\kafka_2.12-2.1.0\logs\__consumer_offsets-30\00000000000000000000.timeindex.cleaned: 另一个程序正在使用此文件,进程无法访问。

    在启动kafka时候报错: java.nio.file.FileSystemException: D:\kafka_2.12-2.1.0\kafka_2.12-2.1.0\logs\__consume ...

  3. Docker启动Elasticsearch报错java.nio.file.AccessDeniedException

    报错信息 Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes 问题分析 表面上是说容 ...

  4. Java JsonPath grab InvalidPathException in code, you must be catching Java 7's java.nio.file.InvalidPathException instead of JsonPath's com.jayway.jsonpath.InvalidPathExceptio

    I am using JsonPath and am able to parse my data and get the values when the path provided is correc ...

  5. Java NIO2 File API介绍

    Introduction to the Java NIO2 File API GitHub NIO2中的文件API是Java 7附带的Java平台的主要新功能之一,特别是新的文件系统API的一个子集以 ...

  6. C#写PDF文件类库PDF File Writer介绍

    .NET平台开源项目速览(16)C#写PDF文件类库PDF File Writer介绍   阅读目录 1.PDF File Writer基本介绍 2.一个简单的使用案例 3.资源 1年前,我在文章:这 ...

  7. java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射

    java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的Io类,不过如果文件超大的话,更快的方式是采用MappedByteBuffer. Mapped ...

  8. 【JavaNIO的深入研究4】内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射

    内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件.有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问.这种解决办法能大大简化修改文件的代码.fileC ...

  9. java获取指定路径下的指定文件/java.io.File.listFiles(FilenameFilter filter)

    java.io.File.listFiles(FilenameFilter filter) 返回抽象路径名数组,表示在目录中此抽象路径名表示,满足指定过滤器的文件和目录. 声明 以下是java.io. ...

  10. java.nio.file.Path

    public interface Path extends Comparable<Path>, Iterable<Path>, Watchable 1. A Path repr ...

随机推荐

  1. 华为云服务器搭建FTP后,内网访问无法连接。

    总结:1.内网访问公网需要用被动模式 2.云服务器需要放开除20.21以外的部分端口,指定范围 3.PASV IP配置为云服务器的弹性公网IP 4.局域网使用主动模式连接FTP,客户端需要关闭防火墙, ...

  2. Must be called at the top of a `setup` function vue3使用vue-i18n时出现的报错

    在某js文件中引入 import {useI18n} from "vue-i18n"; 使用:useI18n().t('APP_LOADING') 修改后: import i18n ...

  3. vue3.0知识整理与总结

    在性能方面,对比Vue2.x,性能提升了1.3~2倍左右:打包后的体积也更小了. 非兼容的功能 一些和Vue2.x版本改动较大的语法,在Vue3存在兼容问题 v-modal   组件通信  v-for ...

  4. appium之手机操作的方法

    Appium内置方法,来操作手机.在做app自动化时,可以内置方法的基本上,对一些常用的手机操作进行方法的封装. 常用的方法有: 获取手机分辨率: driver.get_window(size) # ...

  5. 如何让Macbook Touch Bar 一直显示F1~F12

    原始状态 修改过程 系统偏好设置 > 键盘 将Touch Bar Shous 修改为F1, F2 ,etc. 结果

  6. NodeJs 版本管理

    nvm-windows 说明:nvm是Nodejs的版本管理器.在开发中项目可能需要低版本或者高版本的Nodejs运行环境,以此我们可以使用nvm来切换Nodejs的版本. 在安装NVM for Wi ...

  7. ElasticSearch 实现分词全文检索 - 测试数据准备

    目录 ElasticSearch 实现分词全文检索 - 概述 ElasticSearch 实现分词全文检索 - ES.Kibana.IK安装 ElasticSearch 实现分词全文检索 - Rest ...

  8. 如何在 Apinto 实现 HTTP 与gRPC 的协议转换 (上)

    什么是 gRPC 像gRPC是由google开发的一个高性能.通用的开源 RPC 框架,主要面向移动应用开发且基于HTTP/2协议标准而设计,同时支持大多数流行的编程语言. gRPC基于 HTTP/2 ...

  9. 教你如何用纯css代码实现太极阴阳鱼动画效果

    今天看到一个有意思的效果,闲来无事做一个: 把2d静态的太极图改成了3d,阴极和阳极分到了两个平面里实现旋转效果,这个好实现,重点是实现它的透明效果,平面太极图显示出两极是用另加的块元素挡住底面的颜色 ...

  10. 基于VirutalBox搭建虚拟机间互通的可访问公网的mini主机群

    目标 在单台PC机上安装3+台虚拟机[1] 这些虚拟机间可以相互访问 宿主机[2]与虚拟机可以相互访问 虚拟机可以访问公网 本文将采用 NAT + Host-Only 双网卡的方式实现上述目标,因为采 ...