和低效 IO 说再见,回头补一波 Java 7 的 NIO.2 特性
欢迎点击 “未读代码” ,关注公众号,文章每周更新
抽象文件路径操作方式,直观方便少 BUG。 高效文件操作方式,写入读取复制文件仅需一行。 快速获取不同系统下的文件属性。 遍历目录下文件和目录的多种方式,且十分高效。 反应式事件通知,监测文件变化。
文件路径
Path path = Paths.get("/Users/darcy/java/");
获取一个 Path 对象开始。System.out.println("完整路径:" + path.toString());
Path pathParent = path.getParent();
System.out.println("父级路径:" + pathParent.toString());
Path pathRoot = path.getRoot();
System.out.println("根目录:" + pathRoot.toString());
int pathNameCount = path.getNameCount();
System.out.println("目录深度:" + pathNameCount);
Path pathIndex3 = path.getName(2);
System.out.println("第三级目录:" + pathIndex3);
Path subPath = path.subpath(1, 3);
System.out.println("第1级目录到第三级目录(包左不包右):" + subPath.toString());
// resolveSibling 从当前目录父目录开始拼接目录
Path pathResolveSibling = path.resolveSibling("PathDemo.java");
System.out.println("父目录开始拼接参数:" + pathResolveSibling.toString());
// resolve 把当前路径当作父路径,参数作为子目录或者文件
Path pathResolve = Paths.get("/Users/darcy/java/").resolve("PathDem.java");
System.out.println("当前目录拼接后的目录:" + pathResolve.toString());
// 参数路径相对于主体路径的相对路径
Path path1 = Paths.get("/Users/darcy/");
Path path2 = Paths.get("/Users/darcy/java/PathDemo.java");
Path path3 = path1.relativize(path2);
System.out.println("相对路径:" + path3.toString());
/* 输出结果
完整路径:/Users/darcy/java
父级路径:/Users/darcy
根目录:/
目录深度:3
第三级目录:java
第1级目录到第三级目录(包左不包右):darcy/java
父目录开始拼接参数:/Users/darcy/PathDemo.java
当前目录拼接后的目录:/Users/darcy/java/PathDem.java
相对路径:java/PathDemo.java
*/
文件操作
// 如果文件不存在,则创建一个文件
Path path = Paths.get("test.txt");
Path pathBackup = Paths.get("test_bak.txt");
Path pathLink = Paths.get("test.txt.link");
Path pathDir = Paths.get("dir");
// 已存在则删除
Files.deleteIfExists(path);
Files.deleteIfExists(pathBackup);
Files.deleteIfExists(pathLink);
Files.deleteIfExists(pathDir);
// 创建文件写入内容
Path file = Files.createFile(path);
Files.write(path, "关注公众号:未读代码".getBytes());
Files.write(path, System.lineSeparator().getBytes(), StandardOpenOption.APPEND);
Files.write(path, "欢迎加我微信:wn8398".getBytes(), StandardOpenOption.APPEND);
System.out.println("创建文件:" + file.toString());
// 创建文件链接
pathLink = Files.createLink(pathLink, path);
System.out.println("创建文件:" + pathLink.toString());
// 创建目录
Path directory = Files.createDirectory(pathDir);
System.out.println("创建目录:" + directory.toString());
// 文件复制
Files.copy(path, pathBackup);
System.out.println("复制文件: " + path + " --> " + pathBackup);
// 读取文件
List<String> lines = Files.readAllLines(pathBackup);
for (String line : lines) {
System.out.println("文件读取:" + line);
}
文件属性
java.nio.file.attribute
包内。它抽象出了一个 AttributeView 作为所有属性视图的父接口,然后用它的子类 Fi leAttributeView 表示文件视图,用子类 FileOwnerAttributeView 表示文件所有者的属性视图。前者属性如文件的创建时间、修改时间、是否目录等信息,后者则包含文件的相关信息。为了兼容不同的操作系统,Java 7 还提供了不同实现,如 DosFileAttributeView 视图,很明显他是为 Windows 操作系统准备的。使用起来过于简单,直接代码奉上。
Path path = Paths.get("/Users/darcy/git/jdk-feature/README.md");
BasicFileAttributeView fileAttributeView = Files.getFileAttributeView(path, BasicFileAttributeView.class);
BasicFileAttributes basicFileAttributes = fileAttributeView.readAttributes();
FileTime creationTime = basicFileAttributes.creationTime();
FileTime lastModifiedTime = basicFileAttributes.lastModifiedTime();
FileTime lastAccessTime = basicFileAttributes.lastAccessTime();
System.out.println("创建时间:" + creationTime);
System.out.println("上次修改时间:" + lastModifiedTime);
System.out.println("上次访问时间:" + lastAccessTime);
boolean directory = basicFileAttributes.isDirectory();
boolean regularFile = basicFileAttributes.isRegularFile();
boolean symbolicLink = basicFileAttributes.isSymbolicLink();
System.out.println("是否目录:" + directory);
System.out.println("是否普通文件:" + regularFile);
System.out.println("是否符号链接:" + symbolicLink);
long size = basicFileAttributes.size();
System.out.println("文件大小:" + size);
PosixFileAttributeView linuxFileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
UserPrincipal owner = linuxFileAttributeView.getOwner();
System.out.println("文件归属用户:" + owner.getName());
示例代码运行后得到如下输出。
创建时间:2020-09-06T13:35:14Z
上次修改时间:2020-09-06T13:35:14.649261371Z
上次访问时间:2020-09-06T13:35:14.680968254Z
是否目录:false
是否普通文件:true
是否符号链接:false
文件大小:3636
文件归属用户:darcy
文件列表流
在 Java 7 之前遍历文件目录和文件,你应该会选择 File 类的 listFiles 方法。
// 文件直接遍历,不会遍历子目录
String pathString = "/Users/darcy/project/mylab/src/main/java/com/wdbyte/java";
File file = new File(pathString);
File[] listFiles = file.listFiles();
for (File tempFile : listFiles) {
System.out.println("file list: " + tempFile.getAbsolutePath());
}
这种遍历方式看起来也是十分优雅的,可是这种方式在面对大量文件时,效率会变的很低。所以 Java 7 也对此进行了改进,引入了 DirectoryStream 文件列表流。它可以进行渐进式的文件遍历,每次读取一定数量,降低遍历时的性能开销,但是 DirectoryStream 遍历时只会遍历它的直接目录和文件,不会递归的遍历子目录。下面是它的遍历写法。
String pathString = "/Users/darcy/project/mylab/src/main/java/com/wdbyte/java";
// Path 直接遍历方式,不会遍历子目录
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(pathString))) {
for (Path pathTemp : directoryStream) {
System.out.println("DirectoryStream: " + pathTemp);
}
}
// Path 直接遍历方式 - 筛选 .class 文件
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(pathString), "*.java")) {
for (Path pathTemp : directoryStream) {
System.out.println("DirectoryStream file type is class : " + pathTemp);
}
}
// 遍历所有目录和子目录
Stream<Path> pathStream = Files.walk(Paths.get("/Users/darcy/project/mylab/src/main/java/com/wdbyte"));
pathStream.forEach(pathTemp -> {
System.out.println("Stream: " + pathTemp.toString());
});
// 遍历所有目录和子目录 - 筛选 java 文件
pathStream = Files.walk(Paths.get("/Users/darcy/project/mylab/src/main/java/com/wdbyte"));
pathStream
.filter(pathTemp -> pathTemp.toString().endsWith(".java"))
.forEach(pathTemp -> {
System.out.println("Stream filter java: " + pathTemp.toString());
});
文件监视
// 创建
StandardWatchEventKinds.ENTRY_CREATE,
// 删除
StandardWatchEventKinds.ENTRY_DELETE,
// 更新
StandardWatchEventKinds.ENTRY_MODIFY
/Users/darcy/test
进行监测,注册的感兴趣事件是创建、删除、更新操作。WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get("/Users/darcy/test");
path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey watchKey = watchService.take();
// 获取事件类型
for (WatchEvent<?> pollEvent : watchKey.pollEvents()) {
// 具体的事件上下文信息
Path tempPath = (Path)pollEvent.context();
Kind<?> kind = pollEvent.kind();
if (kind.name().equals(StandardWatchEventKinds.ENTRY_CREATE.name())) {
System.out.println("创建了一个文件:" + tempPath.toString());
}
if (kind.name().equals(StandardWatchEventKinds.ENTRY_DELETE.name())) {
System.out.println("删除了一个文件:" + tempPath.toString());
}
if (kind.name().equals(StandardWatchEventKinds.ENTRY_MODIFY.name())) {
System.out.println("修改了一个文件:" + tempPath.toString());
}
}
// 事件处理完毕后要进行 reset 才能继续监听事件
watchKey.reset();
// 取消监视
// watchKey.cancel();
}
# 下面是我的操作
➜ test pwd
/Users/darcy/test
➜ test touch test.txt # 创建文件
➜ test vim test.txt # 修改文件
➜ test rm test.txt # 删除文件
# 得到的日志输出
创建了一个文件:test.txt
创建了一个文件:.test.txt.swp
修改了一个文件:test.txt
删除了一个文件:.test.txt.swp
删除了一个文件:test.txt
因为使用 vim 编辑,所以有临时的 swp 文件生成和自动删除,也被监测到了。
往期 Java 新特性系列文章:
Java 11 新特性讲解 Java 10 新特性讲解 Java 09 新特性讲解 Java 8 新特性 - 超强的 Stream 流操作姿势 Java 8 新特性 - Lambda 表达式、函数接口 Java 8 新特性 - 时间处理姿势 Java 8 新特性 - 使用 Optional优雅的处理空指针? Java 7 新特性讲解
最后的话
文章有帮助可以点个「在看」或「分享」,都是支持,我都喜欢!
文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 未读代码 」公众号。
---- END ----
"未读代码,一线技术工具人的学习、生活与见闻"
一个「在看」,一段时光👇