zoukankan      html  css  js  c++  java
  • Java7 的 NIO.2

    Java7 对原有的 NIO 进行了重大改进,改进主要包括如下两方面的内容。

    • 提供了全面的文件 NIO 和文件系统访问支持。
    • 基于异步 Channel 的 IO。

    第一个改进表现为 Java7 新增的 java.nio.file 包及各个子包;第二个改进表现为 Java7 在 java.nio.channels 包下增加了多个以 Asynchronous 开头的 Channel 接口和类。Java7 把这种改进称为 NIO.2,本文先详细介绍 NIO 的第二个改进。

    Path、Paths 和 Files 核心 API

    早期的 Java 只提供了一个 File 类来访问文件系统,但 File 类的功能比较有限,它不能利用特定文件系统的特性,File 所提供的方法的性能也不高。而且,其大多数方法在出错时仅返回失败,并不会提供异常信息。

    NIO.2 为了弥补这种不是,引入了一个 Path 接口,Path 接口代表一个平台无关的平台路径。除此之外,NIO.2 还提供了 Files、Paths 两个工具类,其中 Files 包含了大量静态的工具方法来操作文件;Paths 则包含了两个返回 Path 的静态工厂方法。

    提示:Files 和 Paths 两个工具类非常符合 Java 一贯的命名风格,比如前面介绍的操作数组的工具类为 Arrays,操作集合的工具类为 Collections,这种一致的命名风格可以让读者快速了解这些工具类的用途。

    下面程序简单示范了 Path 接口的功能和用法。

    public class PathTest {
        public static void main(String[] args) throws Exception {
            // 以当前路径来创建Path对象
            Path path = Paths.get(".");
            System.out.println("path里包含的路径数量:" + path.getNameCount());
            System.out.println("path的根路径:" + path.getRoot());
            // 获取path对应的绝对路径。
            Path absolutePath = path.toAbsolutePath();
            System.out.println(absolutePath);
            // 获取绝对路径的根路径
            System.out.println("absolutePath的根路径:" + absolutePath.getRoot());
            // 获取绝对路径所包含的路径数量
            System.out.println("absolutePath里包含的路径数量:" + absolutePath.getNameCount());
            System.out.println(absolutePath.getName(3));
            // 以多个String来构建Path对象
            Path path2 = Paths.get("g:", "publish", "codes");
            System.out.println(path2);
        }
    }

    从上面程序可以看出,Paths 提供了 get(String first,String...more) 方法来获取 path 对象,paths 会将给定的多个字符串连缀成路径,比如 Paths.get("g:", "publish", "codes") 就返回 g:publishcodes 路径。

    上面程序中的粗体字代码示范了 Path 接口的常用方法,读者可能对 getNameCount() 方法感到有点困惑,此处简要说明一下:它会返回 Path 路径所包含的路径名的数量,例如 g:publishcodes 调用该方法就会返回3。

    Files 是一个操作文件的工具类,它提供了大量便捷的工具方法,下面程序简单示范了 Files 类的用法。

    public class FilesTest {
        public static void main(String[] args) throws Exception {
            // 复制文件
            Files.copy(Paths.get("FilesTest.java"), new FileOutputStream("a.txt"));
            // 判断FilesTest.java文件是否为隐藏文件
            System.out.println("FilesTest.java是否为隐藏文件:" + Files.isHidden(Paths.get("FilesTest.java")));
            // 一次性读取FilesTest.java文件的所有行
            List<String> lines = Files.readAllLines(Paths.get("FilesTest.java"), Charset.forName("gbk"));
            System.out.println(lines);
            // 判断指定文件的大小
            System.out.println("FilesTest.java的大小为:" + Files.size(Paths.get("FilesTest.java")));
            List<String> poem = new ArrayList<>();
            poem.add("水晶潭底银鱼跃");
            poem.add("清徐风中碧竿横");
            // 直接将多个字符串内容写入指定文件中
            Files.write(Paths.get("pome.txt"), poem, Charset.forName("gbk"));
            // 使用Java 8新增的Stream API列出当前目录下所有文件和子目录
            Files.list(Paths.get(".")).forEach(path -> System.out.println(path));//①
            // 使用Java 8新增的Stream API读取文件内容
            Files.lines(Paths.get("FilesTest.java"), Charset.forName("gbk")).forEach(line -> System.out.println(line));//②
            FileStore cStore = Files.getFileStore(Paths.get("C:"));
            // 判断C盘的总空间,可用空间
            System.out.println("C:共有空间:" + cStore.getTotalSpace());
            System.out.println("C:可用空间:" + cStore.getUsableSpace());
        }
    }

    上面程序中的粗体字代码简单示范了 Files 工具类的用法。从上面程序不难看出,Files 类是一个高度封装的工具类,它提供了大量的工具方法来完成文件复制、读取文件内容、写入文件内容等功能——这些原本需要程序员通过 IO 操作才能完成的功能,现在 Files 类只要一个工具方法即可。

    Java8 进一步增强了 Files 工具类的功能,允许开发者使用 Stream API 来操作文件目录和文件内容,上面示例程序中①号代码使用 Stream API 列出了指定路径下的所有文件和目录;②号代码则使用了 Stream API 读取文件内容。

    注意:读者应该熟练掌握 Files 工具类的用法,它所包含的工具方法可以大大地简化文件IO。

    使用 FileVisitor 遍历文件和目录

    在以前的 Java 版本中,如果程序要遍历指定目录下的所有文件和子目录,则只能使用递归进行遍历,但这种方式不仅复杂,而且灵活性也不高。

    有了 Files 工具类的帮助,现在可以用更优雅的方式来遍历文件和子目录。Files 类提供了如下两个方法来遍历文件和子目录。

    • walkFileTree(Path start, FileVisitor<? super Path> visitor):遍历路径下的所有文件和子目录。
    • walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor):与上一个方法的功能类似。该方法最多遍历 maxDepth 深度的文件。

    上面两个方法都需要 FileVisitor 参数,FileVisitor 代表一个文件访问器,walkFileTree() 方法会自动遍历 start 路径下的所有文件和子目录,遍历文件和子目录都会“触发” FileVisitor 中相应的方法。FileVisitor 中定义了如下4个方法。

    • FileVisitResult postVisitDirectory(T dir,IOException exc):访问子目录之后触发该方法。
    • FileVisitResult preVisitDirectory(T dir,BasicFileAttributes attrs):访问子目录之前触发该方法。
    • FileVisitResult visitFile(T file,BasicFileAttributes attrs):访问 file 文件时触发该方法。
    • FileVisitResult visitFileFailed(T file,IOException exc):访问 file 文件失败时触发该方法。

    上面4个方法都返回一个 FileVisitResult 对象,它是一个枚举类,代表了访问之后的后续行为。FileVisitResult 定义了如下几种后续行为。

    • CONTINUE:代表“继续访问”的后续行为。
    • SKIP_SIBLINGS:代表“继续访问”的后续行为,但不访问该文件或目录的兄弟文件或目录。
    • SKIP_SUBTREE:代表“继续访问”的后续行为,但不访问该文件或目录的子目录树。
    • TERMINATE:代表“中止访问”的后续行为。

    实际编程时没必要为 FileVisitor 的4个方法都提供实现,可以通过继承 SimpleFileVisitor(FileVisitor 的实现类)来实现自己的“文件访问器,这样就根据需要、选择性地重写指定方法了。

    如下程序示范了使用 FileVisitor 来遍历文件和子目录。

    public class FileVisitorTest {
        public static void main(String[] args) throws Exception {
            // 遍历g:publishcodes15目录下的所有文件和子目录
            Files.walkFileTree(Paths.get("g:", "publish", "codes", "15"), new SimpleFileVisitor<Path>() {
                // 访问文件时候触发该方法
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println("正在访问" + file + "文件");
                    // 找到了FileInputStreamTest.java文件
                    if (file.endsWith("FileInputStreamTest.java")) {
                        System.out.println("--已经找到目标文件--");
                        return FileVisitResult.TERMINATE;
                    }
                    return FileVisitResult.CONTINUE;
                }
    
                // 开始访问目录时触发该方法
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    System.out.println("正在访问:" + dir + " 路径");
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }

    上面程序中使用了 Files 工具类的 walkFileTree() 方法来遍历 g:publishcodes15 目录下的所有文件和子目录,如果找到的文件以“FileVisitorTest.java” 结尾,则程序停止遍历——这就实现了对指定目录进行搜索,直到找到指定文件为止。

    使用 WatchService 监控文件变化

    在以前的 Java 版本中,如果程序需要监控文件的变化,则可以考虑启动一条后台线程,这条后台线程每隔一段时间去“遍历”一次指定目录的文件,如果发现此次遍历结果与上次遍历结果不同,则认为文件发生了变化。但这种方式不仅十分烦琐,而且性能也不好。

    NIO.2 的 Path 类提供了如下一个方法来监听文件系统的变化。

    • register(WatchService watcher, WatchEvent.Kind<?>...events):用 watcher 监听该 path 代表的目录下的文件变化。events 参数指定要监听哪些类型的事件。

    在这个方法中 WatchService 代表一个文件系统监听服务,它负责监听 path 代表的目录下的文件变化。一旦使用 register() 方法完成注册之后,接下来就可调用 WatchService 的如下三个方法来获取被监听目录的文件变化事件。

    • WatchKey poll():获取下一个 WatchKey,如果没有 WatchKey 发生就立即返回 null。
    • WatchKey poll(long timeout, TimeUnit unit):尝试等待 timeout 时间去获取下一个 WatchKey。
    • WatchKey take():获取下一个 WatchKey,如果没有 WatchKey 发生就一直等待。

    如果程序需要一直监控,则应该选择使用 take() 方法;如果程序只需要监控指定时间,则可考虑使用 poll() 方法。下面程序示范了使用 WatchService 来监控 C: 盘根路径下文件的变化。

    public class WatchServiceTest {
        public static void main(String[] args) throws Exception {
            // 获取文件系统的WatchService对象
            WatchService watchService = FileSystems.getDefault().newWatchService();
            // 为C:盘根路径注册监听
            Paths.get("C:/").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.context() + " 文件发生了 " + event.kind() + "事件!");
                }
                // 重设WatchKey
                boolean valid = key.reset();
                // 如果重设失败,退出监听
                if (!valid) {
                    break;
                }
            }
        }
    }

    上面程序使用了一个死循环重复获取 c: 盘根路径下文件的变化,程序在①号代码处试图获取下一个 WatchKey,如果没有发生就等待。因此 c: 盘根路径下每次文件的变化都会被该程序监听到。

    运行该程序,然后在 c: 盘下新建一个文件,再删除该文件,将看到如下图所示的输出。

    从上图不难看出,通过使用 WatchService 可以非常优雅地监控指定目录下文件的变化,至于文件发生变化后,程序应该进行哪些处理,这就取决于程序的业务需要了。

    访问文件属性

    早期的 Java 提供的 File 类可以访问一些简单的文件属性,比如文件大小、修改时间、文件是否隐藏、是文件还是目录等。如果程序需要获取或修改更多的文件属性,则必须利用运行所在平台的特定代码来实现,这是一件非常困难的事情。

    Java7 的 NIO.2 在 java.nio.file.attribute 包下提供了大量的工具类,通过这些工具类,开发者可以非常简单地读取、修改文件属性。这些工具类主要分为如下两类。

    • XxxAttributeView:代表某种文件属性的“视图”
    • XxxAttributes:代表某种文件属性的“集合”,程序一般通过 XxxAttributeView 对象来获取 XxxAttributes。

    在这些工具类中,FileAttributeView 是其他 XxxAttributeView 的父接口,下面简单介绍一下这些 XxxAttributeView。

    • AclFileAttributeView:通过 AclFileAttributeView,开发者可以为特定文件设置 ACL(Access Control List)及文件所有者属性。它的 getAcl() 方法返回 List<AclEntry> 对象,该返回值代表了该文件的权限集。通过 setAcl(List) 方法可以修改该文件的 ACL。
    • BasicFileAttributeView:它可以获取或修改文件的基本属性,包括文件的最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。它的 readAttributes() 方法返回一个 BasicFileAttributes 对象,对文件夹基本属性的修改是通过对象完成的。
    • DosFileAttributeView:它主要用于获取或修改文件 DOS 相关属性,比如文件是否只读、是否隐藏、是否为系统文件、是否是存档文件等。它的 readAttributes() 方法返回一个 DosFileAttributes 对象,对这些属性的修改其实是由 DosFileAttributes 对象来完成的。
    • FileOwnerAttributeView:它主要用于获取或修改文件的所有者。它的 getOwner() 方法返回一个 UserPrincipal 对象来代表文件所有者;也可调用 setOwner(UserPrincipal owner) 方法来改变文件的所有者。
    • PosixFileAttributeView:它主要用于获取或修改 POSIX(Portable Operating System Interface of INIX)属性,它的 readAttributes() 方法返回一个 PosixFileAttributes 对象,该对象可用于获取或修改文件的所有者、组所有者、访问权限信息(就是 UNIX 的 chmod 命令负责干的事情)。这个 View 只在 UNIX、Linux 等系统上有用。
    • UserDefinedFileAttributeView:它可以让开发者为文件设置一些自定义属性。

    下面程序示范了如何读取、修改文件的属性。

    public class AttributeViewTest {
        public static void main(String[] args) throws Exception {
            // 获取将要操作的文件
            Path testPath = Paths.get("AttributeViewTest.java");
            // 获取访问基本属性的BasicFileAttributeView
            BasicFileAttributeView basicView = Files.getFileAttributeView(testPath, BasicFileAttributeView.class);
            // 获取访问基本属性的BasicFileAttributes
            BasicFileAttributes basicAttribs = basicView.readAttributes();
            // 访问文件的基本属性
            System.out.println("创建时间:" + new Date(basicAttribs.creationTime().toMillis()));
            System.out.println("最后访问时间:" + new Date(basicAttribs.lastAccessTime().toMillis()));
            System.out.println("最后修改时间:" + new Date(basicAttribs.lastModifiedTime().toMillis()));
            System.out.println("文件大小:" + basicAttribs.size());
            // 获取访问文件属主信息的FileOwnerAttributeView
            FileOwnerAttributeView ownerView = Files.getFileAttributeView(testPath, FileOwnerAttributeView.class);
            // 获取该文件所属的用户
            System.out.println(ownerView.getOwner());
            // 获取系统中guest对应的用户
            UserPrincipal user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest");
            // 修改用户
            ownerView.setOwner(user);
            // 获取访问自定义属性的FileOwnerAttributeView
            UserDefinedFileAttributeView userView = Files.getFileAttributeView(testPath,
                    UserDefinedFileAttributeView.class);
            List<String> attrNames = userView.list();
            // 遍历所有的自定义属性
            for (String name : attrNames) {
                ByteBuffer buf = ByteBuffer.allocate(userView.size(name));
                userView.read(name, buf);
                buf.flip();
                String value = Charset.defaultCharset().decode(buf).toString();
                System.out.println(name + "--->" + value);
            }
            // 添加一个自定义属性
            userView.write("发行者", Charset.defaultCharset().encode("疯狂Java联盟"));
            // 获取访问DOS属性的DosFileAttributeView
            DosFileAttributeView dosView = Files.getFileAttributeView(testPath, DosFileAttributeView.class);
            // 将文件设置隐藏、只读
            dosView.setHidden(true);
            dosView.setReadOnly(true);
        }
    }

    上面程序中的4段粗体字代码分别访问了4种不同类型的文件属性,关于读取、修改文件属性的说明,程序中的代码己有详细说明,因此不再过多地解释。

  • 相关阅读:
    Helpers Overview
    Validation
    Support Facades
    Session Store
    位运算(参考百科)
    开源项目_可能使用到的开源项目集合
    秒杀系统架构分析与实战(转)
    shell命令之根据字符串查询文件对应行记录
    MySQL做为手动开启事务用法
    spring 加载bean过程源码简易解剖(转载)
  • 原文地址:https://www.cnblogs.com/jwen1994/p/12603381.html
Copyright © 2011-2022 走看看