概述

在早期的 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. Java--接口和抽象类有什么区别

    他们都不能实例化对象,都可以包含抽象方法,而且抽象方法必须被继承的类全部实现. 区别: 1.抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实 ...

  2. TP5.1模板循环标签

    第一种volist name=assign中的变量名 id=数组中的key offset=开始循环的位置 length=步长 {volist name='list' id='vo' offset='0 ...

  3. SpringBoot之基于idea搭建ssm(maven)

    此次目标为搭建一个简单的基于springboot的ssm(spring + springmvc + mybatis)的maven项目,使用的数据库为mysql. 最终项目目录结构 创建过程 1.项目创 ...

  4. 关于k8s微服务的基础知识分享总结

    1.说起k8s,先得讲讲微服务,来个图(百度上找到的图),初识 1.微服务架构强调的是一种架构模式,提倡将单一的应用程序,划分为一组小的服务,每个服务运行在其独立的自己的进程中,服务之间相互协调配合, ...

  5. 学会规则引擎Drools,让你早点下班

    前言 现在有这么个需求,网上购物,需要根据不同的规则计算商品折扣,比如VIP客户增加5%的折扣,购买金额超过1000元的增加10%的折扣等,而且这些规则可能随时发生变化,甚至增加新的规则.面对这个需求 ...

  6. RTE2021 回顾丨实践中的摸爬滚打,AI OPS 落地之路

    本文整理自声网Agora SD - RTN 网络传输质量负责人于涛在 RTE2021 实时互联网大会上的演讲分享.他在演讲中针对传统 OPS 痛点.AI OPS 的优势以及 AI OPS 工程化的难点 ...

  7. java中foreach循环用法详解

    前言 在前面的文章中,千锋壹哥给大家讲解了for.while.do-while三种循环结构,并讲解了如何跳出循环的几种方式,比如break.continue.return等.但是截止到目前,与循环相关 ...

  8. uniapp中easycom用法详解

    Uniapp中的easycom是一种组件自动注册机制,可以让开发者更加方便地使用和管理组件.下面详细介绍下关于easycom使用方法. 什么是easycom? easycom是Uniapp框架提供的一 ...

  9. python + BeautifulSoup + selenium 实现爬取中医智库的古籍分类的数据

    爬取内容为 该图片下的七个分类, 然后对应的每个种类的书本信息(摘要和目录) 效果为 代码如下 import requests from bs4 import BeautifulSoup import ...

  10. Tarjan强连通分量(scc)

    概念解释 节点强连通:\(v_i\)与\(v_j\)(\(v_i ≠ v_j\))强连通是指从\(vi\)到\(vj\)和从\(vj\)到\(vi\)都存在路径,即两节点互相可达 强连通图:在有向图\ ...