关于java:和低效-IO-说再见回头补一波-Java-7-的-NIO2-特性

5次阅读

共计 8096 个字符,预计需要花费 21 分钟才能阅读完成。

其实在这之前曾经写过一篇对于 Java 7 的新个性文章了,那篇文章次要介绍了 Java 7 的资源主动敞开、Switch String 实现原理、异样捕捉 try-catch、新的二进制书写形式等,具体的内容也能够看下当初的这篇文章(补一波 Java 7 语法个性)。而在那篇文章里唯独没有介绍到 Java 7 中对于 IO 操作的更新,而这部分恰好又是十分重要的一部分,该还的总是要还的,当初补上。

看完这篇文章你会理解到:

  1. 形象文件门路 操作形式,直观不便少 BUG。
  2. 高效的文件操作形式,写入读取复制文件仅需一行
  3. 疾速获取不同零碎下的 文件属性
  4. 遍历目录下文件和目录的 多种形式,且非常高效。
  5. 反应式 事件告诉,监测文件变动。

在 Java 7 中,增强了文件操作相干性能,也就是新的 java.nio.file 包里的内容,它提供了诸如文件门路形象、文件目录流、目录树、文件属性和变动监督服务等性能,能够大幅度提高咱们对于文件的操作。

文件门路

在 Java 7 之前对 文件门路 的操作都是以 字符串 的操作,应用时你须要把一个字符串间接扔进去,间接应用字符串操作是低效的,比方你要拼接父门路和子目录,你只能进行字符串的拼接。而且拼接这个自身操作就失落了它作为文件门路的含意。另外应用字符串进行各种门路操作很有可能因为拼写错误而呈现各种问题。

Java 7 的到来让这所有变的不一样了,它提供了 Path 接口用来示意门路的形象,而后提供了一系列对于门路的操作方法,让这所有变得如此简略。

为了不便的创立 Path 对象,又提供了 Paths 工具类,如何应用让咱们先睹为快。

所有都从 Path path = Paths.get("/Users/darcy/java/"); 获取一个 Path 对象开始。

Path path = Paths.get("/Users/darcy/java/");
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 中的办法进行操作的,在此之前你可能须要各种字符串截取拼接,非常繁琐。

文件操作

还记得初学 Java IO 时,文件复制有多种写法,然而不论是哪一种,写起来都须要不少的代码,而且还须要思考复制时的性能。读取文件那就更不用说了,定义各种读取和接管变量,各种验证。当初不一样了,不仅文件操作十分不便,而且像文件 复制和读取 等罕用操作都能够 一行搞定

应用过于简略,间接代码。

// 如果文件不存在,则创立一个文件
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);
}

下面展现了 Files 类的文件创建、删除、写入、拷贝、读取的写法,都是只有一行代码。

文件属性

和门路操作相似,Java 7 也提供了文件属性的形象,减少了一系列文件属性的操作工具类。这部分代码在 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);
    }
}

这里扩大一下,在 Java 8 中对 Files 类进行了加强,引入了 Java 8 的 Lambda 表达式,减少了 walk 办法,遍历文件也有殊途同归之妙(上面的例子中用到了 Lambda 表达式)。

// 遍历所有目录和子目录
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());
    });

文件监督

文件监督,也就是能够动静的监测指定目录的文件或者内容的变动,利用场景很多,比方热部署时查看 class 文件是否更新,或者每当有文件进来时就进行操作。在这之前你只能通过循环调用 listFiles 并与上次的调用后果比照才能够找到文件的变动,而当初能够通过告诉的形式进行反应式的逻辑解决,所有变的简略了。

被监督的对象要实现 Watchable 接口,而后通过 register 办法注册到监督服务 WatchService 接口的实现,同时指定要监督的事件类型。

// 创立
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();}

注册事件监听后,通过一个循环,调用 take() 办法获取事件后果,失去事件后再判断事件类型进行日志输入。我启动后进行了简略测试,上面是日志输入。

# 上面是我的操作
➜  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 新个性解说

最初的话

文章曾经收录在 Github.com/niumoo/JavaNotes,欢送 Star 和指教。更有一线大厂面试点,Java 程序员须要把握的外围常识等文章,也整顿了很多我的文字,欢送 Star 和欠缺,心愿咱们一起变得优良。

文章有帮忙能够点个「」或「 分享 」,都是反对,我都喜爱!
文章每周继续更新,要实时关注我更新的文章以及分享的干货,能够关注「未读代码」公众号或者我的博客。

正文完
 0