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:\Java\chap15\.
        //获取绝对路径的根路径
        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:\publish\codes
    }
}


从上面的程序可以看出,Paths提供了get(String first,String...more)方法来获取Path对象,Path会将多个给定的字符串连接成路径,比如Paths.get("g:","publish","codes");返回g:\publish\codes。getNameCount()方法用于返回Path路径所包含的路径名数量,例如g:\publish\codes返回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);//src\FileTest.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:\publish\codes\15目录下的所有文件和子目录
        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:\codes\15 路径
正在访问:g:\codes\15\15.1 路径
正在访问g:\codes\15\15.1\1546017388045文件
正在访问g:\codes\15\15.1\FilenameFilterTest.class文件
正在访问g:\codes\15\15.1\FilenameFilterTest.java文件
正在访问g:\codes\15\15.1\FileTest.class文件
正在访问g:\codes\15\15.1\FileTest.java文件
正在访问:g:\codes\15\15.10 路径
正在访问g:\codes\15\15.10\a.txt文件
正在访问g:\codes\15\15.10\AttributeViewTest.class文件
正在访问g:\codes\15\15.10\AttributeViewTest.java文件
正在访问g:\codes\15\15.10\FilesTest.class文件
正在访问g:\codes\15\15.10\FilesTest.java文件
正在访问g:\codes\15\15.10\FileVisitorTest$1.class文件
正在访问g:\codes\15\15.10\FileVisitorTest.class文件
正在访问g:\codes\15\15.10\FileVisitorTest.java文件
正在访问g:\codes\15\15.10\PathTest.class文件
正在访问g:\codes\15\15.10\PathTest.java文件
正在访问g:\codes\15\15.10\pome.txt文件
正在访问g:\codes\15\15.10\WatchServiceTest.class文件
正在访问g:\codes\15\15.10\WatchServiceTest.java文件
正在访问:g:\codes\15\15.3 路径
正在访问g:\codes\15\15.3\FileInputStreamTest.class文件
正在访问g:\codes\15\15.3\FileInputStreamTest.java文件
--已经找到目标文件--

上面程序使用Files工具类的walkFileTree()方法来遍历G:\codes\15目录下的所有文件和子目录,如果找到文件以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);
    }
}
posted @ 2020-05-09 17:37  小新和风间  阅读(276)  评论(0编辑  收藏  举报