• 12.10 NIO.2的功能和用法



    Java 7对原有的NIO进行了重大改进,改进的主要内容主要包括以下两个方面:
    1、提供了全面的文件IO和文件系统访问支持
    2、基于异步Channel的IO。
    第一个改进表现为Java 7 新增的java.nio.file包及其各个子包;第二个改进表现为Java 7在java.nio.channels包下新增的多个以Asynchronous开头的Channel接口和类。Java 7 把这种改进称为NIO.2。本节先介绍第二个改进

    一、Path、Paths和Files核心API

    传统的Java里,只有一个File类,即代表文件,又代表目录。但File类的功能比较有限,它不能利用特定文件系统的特性,File提供的方法性能也不高。而且大多数方法在出错时仅返回失败,并不会提供异常信息。
    NIO.2为了弥补这种不足,Java 7新增了如下API来访问文件
    (1)Path - 接口,代表一个平台无关的平台路径。提供了大量的方法来操作目录。
    (2)Paths - 工具类。包含了两个返回path的静态工厂方法。
    (2)Files - 包含了大量的静态的工具方法来操作文件。

    1.1 Paths工具类与path使用举例

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

    package section10;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    public class PathTest
    {
        public static void main(String[] args) throws Exception
        {
            //以当前路径创建path对象
            Path path= Paths.get(".");
            System.out.println("path里包含的路径数量:"+path.getNameCount());//1
            System.out.println("path的根路径:"+path.getRoot());//null
            //获取path对应的绝对路径
            Path absolutePath=path.toAbsolutePath();
            System.out.println(absolutePath);//E:Javachap15.
            //获取绝对路径的根路径
            System.out.println("absolutePath的根路径:"+absolutePath.getRoot());//E:
            //获取绝对路径所包含的路径数量
            System.out.println("absolutePath里包含的路径数量:"+absolutePath.getNameCount());//3
            for(int i=0;i<absolutePath.getNameCount();i++)
            {System.out.println(absolutePath.getName(i));}
            //Java
            //chap15
            //.
            //一多个String来构建Path对象
            Path path1=Paths.get("g:","publish","codes");
            System.out.println(path1);//g:publishcodes
        }
    }
    


    从上面的程序可以看出,Paths提供了get(String first,String...more)方法来获取Path对象,Path会将多个给定的字符串连接成路径,比如Paths.get("g:","publish","codes");返回g:publishcodes。getNameCount()方法用于返回Path路径所包含的路径名数量,例如g:publishcodes返回2,分别为publish,codes.

    1.2 Files工具类与File使用举例

    Files是一个工具类,她提供了大量的便捷的工具方法,下面程序简单示范了Files类的用法:

    import java.io.FileOutputStream;
    import java.nio.charset.Charset;
    import java.nio.file.FileStore;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    public class FileTest
    {
        public static void main(String[] args)
                throws Exception
        {
            //复制文件
            Path path= Paths.get("src","FileTest.java");
            System.out.println(path);//srcFileTest.java
            Files.copy(path,new FileOutputStream("src//a.txt"));
    
            //判断FileTest.java是否为隐藏文件
            System.out.println("FileTest.java是否为隐藏文件:"+Files.isHidden(path));//false
    
            //一次性读取FileTest.java文件的所有行
            List<String> lines=Files.readAllLines(path);
            System.out.println(lines.get(0));//import java.io.FileOutputStream;
    
            //判断文件大小
            System.out.println("FileTest.java文件的大小:"+Files.size(path));//1826
    
            List<String> poem=new ArrayList<>();
            poem.add("窗前明月光");
            poem.add("疑似地上霜");
            //直接将多个字符串内容写进指定文件
            Files.write(Paths.get("src","poem.txt"),poem, Charset.forName("utf-8"));
    
            //使用Java8新增的Stream API列出当前目录下所有文件和子目录
            Files.list(Paths.get(".")).forEach(path1 -> System.out.println(path1));//代码1
    
            //使用Java8新增得Stream API读取文件内容
            Files.lines(path,Charset.forName("utf-8")).forEach(line->System.out.println(line));//代码2
    
            //判断C盘总空间、可用空间
            FileStore cstore=Files.getFileStore(Paths.get("C:"));
            System.out.println("C盘总空间:"+cstore.getTotalSpace());//85900390400
            System.out.println("C盘可用空间:"+cstore.getUsableSpace());//43449065472
    
        }
    }
    

    Files类是一个高度封装得工具类,它提供大量的工具方法来完成文件得复制、读取文件、写入文件等功能——这些原本需要程序员通过IO操作才能完成的功能,现在只需一个Files工具类即可。

    二、使用FileVisitor遍历文件和目录

    Files工具类提供两个方法来遍历文件和子目录:
    (1)walkFileTree(Path start,FileVisitor<? super path>visitor):遍历start路径下所有文件和目录。
    (2)walkFileTree(Path start,Set options,int maxDepth,FileVisitor<? super path>visitor):与上一个方法类似,该方法最多遍历maxDepth深度的文件。
    上面两个方法都需要FileVisitor参数,FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法。FileVisitor中定义了4个方法:

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

    上面4个方法都返回一个FileVisitResult对象,它是一个枚举类,代表访问值后的后续行为。

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

    实际编程中没必要为FileVisitor中的4个方法都提供实现,可以通过继承SimpleFileVisitor(FileVisitor的实现类)来实现自己的文件访问器,这样就根据需要、选择性地重写指定方法。
    下面程序使用了FileVisitor来遍历文件和子目录:

    import java.io.IOException;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    
    public class FileVisitorTest
    {
        public static void main(String[] args)
                throws Exception
        {
            // 遍历g:publishcodes15目录下的所有文件和子目录
            Files.walkFileTree(Paths.get("g:","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;
                        }
                    });
        }
    }
    正在访问:g:codes15 路径
    正在访问:g:codes1515.1 路径
    正在访问g:codes1515.11546017388045文件
    正在访问g:codes1515.1FilenameFilterTest.class文件
    正在访问g:codes1515.1FilenameFilterTest.java文件
    正在访问g:codes1515.1FileTest.class文件
    正在访问g:codes1515.1FileTest.java文件
    正在访问:g:codes1515.10 路径
    正在访问g:codes1515.10a.txt文件
    正在访问g:codes1515.10AttributeViewTest.class文件
    正在访问g:codes1515.10AttributeViewTest.java文件
    正在访问g:codes1515.10FilesTest.class文件
    正在访问g:codes1515.10FilesTest.java文件
    正在访问g:codes1515.10FileVisitorTest$1.class文件
    正在访问g:codes1515.10FileVisitorTest.class文件
    正在访问g:codes1515.10FileVisitorTest.java文件
    正在访问g:codes1515.10PathTest.class文件
    正在访问g:codes1515.10PathTest.java文件
    正在访问g:codes1515.10pome.txt文件
    正在访问g:codes1515.10WatchServiceTest.class文件
    正在访问g:codes1515.10WatchServiceTest.java文件
    正在访问:g:codes1515.3 路径
    正在访问g:codes1515.3FileInputStreamTest.class文件
    正在访问g:codes1515.3FileInputStreamTest.java文件
    --已经找到目标文件--
    

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

    三、使用WatchService监控文件变化

    在以前的Java版本中,如果程序需要监控文件的变化,则可以考虑启动一条后台线程,这条线程每个一段时间就去“遍历”一次指定目录的文件,如果发现次次遍历结果与上次遍历结果不同,则认为文件发生了变化。但是这种方式不仅十分繁琐,而且性能不好。
    NIO.2的Path类提供了一个方法来监听文件系统的变化
    register(WatchService watcher,WatchEvent.Kind<?>...events):用watcher监听该path代表的目录下的文件变化。event参数指定要监听哪些类型的事件。
    在这个方法中WatchService代表一个文件监听服务,它负责监听path代表的目录下的文件变化。一旦register完成注册,接下来接可以调用WatchService的如下三个方法来监听目录的文件变化事件。
    (1)WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生就立即返回null。
    (2)WatchKey poll(long timeout,TimeUnit unit):尝试timeout事件去获取下一个WatchKey。
    (3)WatchKey take():获取下一个WatchKey,如果没有WatchKey发生就一直等待。
    如果一条程序需要一直监控,应该选用take()方法;如果程序只需要监控指定时间,则可以考虑使用poll()方法。下面程序示范了WatchService来监控C:盘更路径下文件的变化:

    
    import java.io.*;
    import java.nio.file.*;
    import java.nio.file.attribute.*;
    
    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;
                }
            }
        }
    }
    新建文件夹 文件发生了 ENTRY_CREATE事件!
    新建文件夹 文件发生了 ENTRY_DELETE事件!
    

    上面程序使用了一个死循环重复获取C:盘根路径下文件的变化,程序在①处试图获取下一个WatchKey,如果没有发生,就等待,因此C:盘根路径下的每次文件的变化都会被该程序监听到。运行该程序,然后再C:盘下新建一个文件,再删除该文件,可以得到上面的输出。

    三、访问文件属性

    早期Java提供File类可以访问一些简单的文件属性,比如文件大小、修改时间、文件是否隐藏,是文件还是目录。如果程序需要获取或修改更多的文件属性,额必须利用运行所在平台特定代码来实现,这是一件非常困难的事。
    Java 7的NIO2在java.nio.file.attribute包下提供了大量工具类,通过这些工具类,开发者可以很简单地读取、修改文件属性。这些工具类主要分为两类:
    ★XxxAttributeView:代表某种文件的“视图”。
    ★XxxAtrributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView对象来获取XxxAttributes。
    这些工具类中,FileAttributeView是其他XxxAttributeView的父接口,下面简单介绍一下这些XxxAttributeView。
    1、AclFileAttributeView:通过AclFileAttributeView,开发者可以为特定文件设置ACL(Access Control List)及文件所有者属性。它的getAcl()方法返回List对象,该返回值代表了该文件的权限集。通过setAcl(List)方法可以返回该文件的ACL。
    2、BasicFileAttributeView:它可以获取或修改文件的基本属性,包括文件最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。它的readAttributes()方法返回一个BasicFileAttributes对象,对文件夹基本属性的修改是通过BasicFileAttributes来完成的。
    3、DosFileAttributeView:它主要获取或修改文件DOS相关的属性,比如文件是否可读、是否隐藏、是否为系统文件、是否是存档文件等。它的readAttributes()方法返回一个DosFileAttributes对象,对这些属性的修改其实是通过DosFileAttributes对象来完成的。
    4、FileAttributeView:它主要用于获取或修改文件的所有者。它的getOwner()方法返回一个UserPrincipal对象来代替文件所有者;也可通过调用setOwer(UserPricipal owner)方法来改变文件的所有者。
    5、PosixFileAttributeView:它主要用于获取或修改POSIX(Portable Operating System Interface of INIX)属性,它的readAttributes()方法返回一个PosixFileAttributes对象,该对象用于获取或修改文件的所有者、组所有者、访问权限信息/这个View只在UNIX、Linux等系统上有用。
    6、UserDefinedFileAttributeView:它可以让开发者为文件设置一些自定义属性。
    下面程序示范了如何读取、修改文件的属性:

    
    import java.io.*;
    import java.util.*;
    import java.nio.file.*;
    import java.nio.*;
    import java.nio.charset.*;
    import java.nio.file.attribute.*;
    
    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 (var 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);
        }
    }
    
  • 相关阅读:
    PHP开发经常遇到的几个错误
    PHP的Trait
    PHP反射API
    php split 和 explode 的区别
    php判断检测一个数组里有没有重复的值
    PHP serialize 和 JSON 解析与区别
    php 单文件上传
    php 数组 类对象 值传递 引用传递 区别
    六. 网络编程(解决黏包TCP)
    五. 网络编程(UDP 不黏包)
  • 原文地址:https://www.cnblogs.com/weststar/p/12839835.html
Copyright © 2020-2023  润新知