Java--NIO2文件系统

JDK7提出了NIO2文件系统API,存取了默认文件系统进行各种输入/输出的API,既可简化现有的文档输入/输出API操作,也增加了许多过去没有提供的文件系统存取功能。


NIO2架构

在JDK7出现之前,常要针对特定文件系统攥写特定程序,不仅攥写方式没有标准,针对特定功能攥写程序也会增加应用程序开发者的负担。

NIO2文件系统API提供一组标准接口和类,应用程序开发者只要基于这些标准接口与类进行文件系统操作,底层实际如何进行文件系统操作,是由文件系统提供者负责。

应用程序开发者主要使用java.nio.file与java.nio.file.attribute, 包中必须操作的抽象类或接口,由文件系统提供者操作,只有文件系统提供者才关心java.nio.file.spi包。

NIO2文件系统的中心是java.nio.file.spi.FileSystemProvider,本身为抽象类,是文件系统提供者才要操作的类,作用是产生java.nio.file与java.nio.file.attribute中各种抽象类或接口的操作对象。

下图是FileSystemProvider产生的各种操作对象:
这里写图片描述

对于应用程序开发者,只需知道有这个东西的存在即可。应用程序开发者可以通过一些类提供的静态方法,取得相关操作对象或进行各种文件系统操作,这些静态方法内部会运用FileSystemProvider来取得所需的操作对象,完成应有的操作。

例如想要取得java.nio.file.FileSystem操作对象,可以通过FileSystems.getDefault()来取得:

FileSystem fileSystem = FileSystems.getDefault();

操作路径

想要操作文档,就得先指出文档路径。Path实例是在JVM中路径的代表对象,也是NIO2文件系统API操作的起点,NIO2文件系统API中有许多操作,都必须使用Path指定路径。

想要取得Path实例,可以使用Paths.get()方法。最基本的使用方式,就是使用字符串路径,可以使用绝对路径,也可以使用相对路径。例如:

Path workspace = Paths.get("C:\\workspace");
Path books = Paths.get("Desktop\\books");
Path path = Paths.get(System.getProperty("user.home"), "Documents", "Downloads");

Paths.get()的第二个参数开始接受不定长度自变量,因此可以指定起始路径,之后的路径分段指定。
如果用户目录是C:\Users\Justin, 那么以上第三个Path实例代表的路径就是C:\Users\Justin\Documents\Downloads。

Path实例仅代表路径信息,该路径实际对应的文档或文件夹不一定存在。Path提供一些方法取得路径的各种信息。例如:

public class PathDemo {
    public static void main(String[] args){
        Path path = Paths.get(System.getProperty("user.home"), "git", "Linux");

        out.printf("toString: %s\n", path.toString());
        out.printf("getFileName: %s\n", path.getFileName());
        out.printf("getName(0): %s\n", path.getName(0));
        out.printf("getNameCount: %d\n", path.getNameCount());

        //Path.subpath(int a, int b)是用来将a,b-1之间的部分进行截取
        out.printf("subpath(0, 2): %s\n", path.subpath(0, 2));
        out.printf("getParent: %s\n", path.getParent());
        out.printf("getRoot: %s\n", path.getRoot());
    }
}

路径元素是以文件夹为单位的,最上层文件夹为索引0。

Path操作了Iterable接口,若要循序取得Path中分段的路径信息,也可以使用增强式for循环或JDK8新增的forEach(out::println);

Path的toAbsolutePath()方法可以将相对路径转为绝对路径,如果路径是符号链接,使用toRealPath方法可以转为真正的路径,若是相对路径则转换为绝对路径,若路径中由冗余信息也会移除。

路径与路径可以使用resolve方法进行结合。

如果想知道如何从一个路径切换至另一个路径,则可以使用relativize方法。

equals方法比较两个Path路径是否相同,startsWith方法比较路径起始是否相同,endsWith方法比较路径结尾是否相同,如果文件系统支持符号链接,那么就算路径不同也有可能指的是同一文档,我们可以使用Files.isSameFile方法判断。

可以使用Files.exists方法判断路径所指的文档是否存在,如果存在就返回true,如果不存在或无法读取(没有存取权限)会返回false,Files.notExists方法和它刚好相反。


属性读取与设定

在过去并没有标准的方式取得不同文件系统支持的不同属性,在JDK7中,可以通过BasicFileAttributes,DosFileAAttributes,PosixFileAttributes针对不同的文件系统取得支持的属性,后两个接口继承自第一个接口。

public class BasicFileAttributesDemo {
    public static void main(String[] args) throws IOException{
        Path path = Paths.get("/home/paranoid/git/Linux");
        BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

        out.printf("creationTime: %s\n", attributes.creationTime());
        out.printf("lastAccessTime: %s\n", attributes.lastAccessTime());
        out.printf("lastModifiedTime: %s\n", attributes.lastModifiedTime());
        out.printf("isDirectory: %b\n", attributes.isDirectory());
        out.printf("isOther: %b\n", attributes.isOther());
        out.printf("isRegularFile: %b\n", attributes.isRegularFile());
        out.printf("isSymbolicLink: %b\n", attributes.isSymbolicLink());
        out.printf("size: %d\n", attributes.size());
    }
}

creationTime(),lastAccessTime(),lastModifiedTime()返回的是FileTime实例,也可以通过getLastModifiedTime()取得最后修改的时间。

属性设定主要通过Files.setAttribute方法。

Files.setAttribute方法的第二个自变量必须指定FileAttributeView子接口规范的名称,格式为[view-name:]attribute-name。view-name可以从FileAttributeView子接口操作对象的name()方法取得,如果省略就默认为“basic”,attribute-name可在FileAttributeView各子接口的API文件中查询。如:同样设定最后修改时间,Files.setAttribute方法可以这样攥写:

long currentTime = System.currentTimeMillis();
FileTime filetime = FileTime.fromMillis(currentTime);
Files.setAttribute(Paths.get("/home/paranoid/git"), "basic: lastModifiedTime", filetime);

DosFileAAttributes,PosixFileAttributes相关API有兴趣的同学可以下去了解。

如果想要取得存储装置本身的信息,可以利用Files.getFileStore方法取得指定路径的FileStore 实例。通过FileSystem的getFileStores方法取得所有存储装置的FileStore实例,如:

public class Disk {
    public static void print(FileStore fileStore) throws IOException{
        long total = fileStore.getTotalSpace();
        long used = fileStore.getUnallocatedSpace();
        long usable = fileStore.getUsableSpace();
        DecimalFormat format = new DecimalFormat("#, ###, ###");

        out.println(fileStore.toString());
        out.printf("总容量: %s字节\n", format.format(total));
        out.printf("可用空间: %s字节\n", format.format(used));
        out.printf("已用空间: %s字节\n", format.format(usable));
    }

    public static void main(String[] args) throws IOException{
        if(args.length == 0){
            FileSystem fileSystem = FileSystems.getDefault();

            //通过getFileStores方法取得所有存储装置FileStore实例
            for(FileStore store : fileSystem.getFileStores()){
                print(store);
            }
        }else{
            for(String file : args){
                FileStore fileStore = Files.getFileStore(Paths.get(file));
                print(fileStore);
            }
        }
    }
}

FileSystem的getFileStores方法会以iterable返回所有存储装置的FileStore对象。


操作文档与目录

如果想要删除Path代表的文档或目录,可以使用Files.delete方法,如果不存在,会抛出NoSuchFileException,如果因为目录不为空而无法删除文档,会抛出DirectoryNotEmptyException。使用Files.deleteIfExists方法删除文档时,如果文档不存在,并不会抛出异常。

如果想要复制来源Path的文档或目录至目的地Path,可以使用Files.copy(),这个方法的第三个参数可以指定CopyOption接口的操作对象。具体的希望大家可以下去了解,举个例子:

Path src = ...;
Path dest = ...;
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);

Files.copy()还有两个重载版本。一个是接受InputStream作为来源,可直接读取数据,并将结果复制在指定的Path中,一个是将源Path复制至指定的OutputStream。例如:

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class NIO2Download {
    public static void main(String[] args) throws IOException{
        URL url = new URL(args[0]);

        //REPLACE_EXISTING如目标文档存在就会覆盖
        Files.copy(url.openStream(), Paths.get(args[1]), REPLACE_EXISTING);
    }
}

若要进行文档或目录的移动,可以使用Files.move()方法,使用方式与Files.copy()方法类似,如果文件系统支持原子移动,可在移动时指定StandardCopyOption.ATOMIC_MOVE选项。

如果要建立目录,可以使用Files.createDirectory()方法,如果调用时父目录不存在,会抛出NoSuchFileException。Files.createDirectories()会在父目录不存在时一并建立。

如果要建立暂存目录,可以使用Files.createTempDirectory(),这个方法有可指定路径与使用默认路径建立暂存目录两个版本。

如果文档都是字符,则需要在读取或写入时使用缓冲区,也可以使用Files.newBufferedReader(),Files.newBufferedWriter()指定文档Path和编码,他们分别会返回BufferedReader(),BufferedWriter实例,可以使用他们进行文档读取和写入。例如如果原先有个建立BufferedReader的片段如下:

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), "utf-8"));

可使用Files.BufferedReader()改写如下:

BufferedReader reader = Files.newBufferedReader(Paths.get(args[0]), "utf-8");

如果想要以InputStream和OutputStream处理相关文档,也有相对应的Files.newInputStream和Files.newOutputStream。


读取,访问目录

如果想要获得文件系统根目录路径信息,可以使用FileSystem的getRootDirectories()方法,这回取回Iterable对象。例如:

public class Roots {
    public static void main(String[] args){
        Iterable<Path> iterable = FileSystems.getDefault().getRootDirectories();

        iterable.forEach(out::println);
    }
}

也可以使用Files.newDirectoryStream()方法取得DirectoryStream()接口操作对象,代表指定路径下的所有文档。

DirectoryStream可以使用尝试关闭资源语法。Files.newDirectoryStream()实际返回的是DirectoryStream<Path>。

下面这个范例可以从命令行自变量指定目录中,查询出该目录下的文档:

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.lang.System.out;

public class Dir{
    public static void main(String[] args) throws IOException{

        //尝试自动关闭资源获取目录下的文档
        try (DirectoryStream<Path> directoryStream =
                     Files.newDirectoryStream(Paths.get(args[0]))){
            List<String> list = new ArrayList<>();

            for(Path path : directoryStream){
                if(Files.isDirectory(path)){
                    out.printf("[%s]\n", path.getFileName());
                }else{
                    list.add(path.getFileName().toString());
                }
            }

            list.forEach(out :: println);
        }
    }
}

如果想要访问目录中所有的文档和子目录,可以调用FileVisitor接口,其中定义了4个必须操作的方法,如果只对其中的一两个方法有兴趣,可以继承SimpleFileVisitor类,这个类操作了FileVisitor接口,只要继承之后重新定义感兴趣的方法就可以了。

从指定的目录路径开始,每次要访问该目录的内容前,都会调用previsitDirectory(),要访问文档时会调用visitFile(),访问文档失败会调用visitFileFailed(),访问整个目录内容后会调用postVisitDirectory(),如果有多层目录,如下图:(递归)
这里写图片描述

可以使用Files.walkFileTree()访问目录,例如:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

import static java.lang.System.err;
import static java.lang.System.out;
import static java.nio.file.FileVisitResult.CONTINUE;

public class ConsoleFileVisitor extends SimpleFileVisitor<Path>{
    @Override
    public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attributes)
        throws IOException{
        printSpace(path);
        out.printf("[%s]\n", path.getFileName());

        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path path, BasicFileAttributes attributes){
        printSpace(path);
        out.printf("%s\n", path.getFileName());

        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc){
        err.println(exc);

        return CONTINUE;
    }

    private void printSpace(Path path){
        out.printf("%" + path.getNameCount()*2 + "s", "");
    }
}
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DirAll {
    public static void main(String[] args) throws IOException{
        Files.walkFileTree(Paths.get(args[0]), new ConsoleFileVisitor());
    }
}

在NIO2的目录访问上,新增了list(),walk(),list方法显示当前目录下的所有文档,walk方法显示当前目录下及子目录下所有的文档。都可以使用尝试自动关闭资源。

try(Stream<Path> paths = Files.list(Paths.get(args[0]))){
    paths.forEach(out::println);
}

过滤,搜索文档

如果想在列出目录内容时过滤想显示的文档,例如只想显示.class和.jar文档,就可以使用Files.newDirectoryStream()时的第二个参数指定过滤条件为*.{class,jar}。例如:

try(DirectoryStream<path> directoryStream = Files.newDirectoryStream(Paths.get(args[0]), "*.{class, jar}")){
    directoryStream.forEach(path -> out.println(path.getFilename));
}

上面的*.{class, jar}是Glob语法,比正则表达式更简单,常用于目录与文件名的比较。有兴趣的同学可以多了解一下。

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.lang.System.out;

public class Ls {
    public static void main(String[] args) throws IOException{
        //默认取得所有文档
        String glob = args.length == 0 ? "*" : args[0];

        //获取当前工作目录
        Path uerPath = Paths.get(System.getProperty("user.dir"));
        try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(uerPath, glob)){
            directoryStream.forEach(path -> out.println(path.getFileName()));
        }
    }
}

如果启动JVM时指定命令行自变量为build*,表示使用Glob语法为build*,那么工作目录下所有build开头的文档或目录都会显示出来。

使用FileSystem实例的getPathMatcher()取得PathMatcher接口操作对象,在取得PathMatcher时可以指定使用那种模式比较语法,“regex”表示使用规则表达式,“glob”表示使用glob语法。例如:

PathMatcher pathMatcher = FileSystem.getDefault().getPathMatcher("glob:*.{class, jar}");

取得PathMatcher之后,可以使用matches方法进行路径比较,返回true表示符合模式,来看一个范例:

import java.io.IOException;
import java.nio.file.*;

import static java.lang.System.out;

public class Ls2 {
    public static void main(String[] args) throws IOException{
        //默认使用glob模式
        String syntax = args.length == 2? args[0] : "glob";
        String pattern = args.length == 2? args[1] : "*";

        out.println(syntax + ":" + pattern);

        //取得当前的工作目录
        Path userPath = Paths.get(System.getProperty("user.dir"));

        //按照输入方式进行搜索
        PathMatcher pathMatcher = FileSystems.getDefault().
                getPathMatcher(syntax + ":" + pattern);

        try (DirectoryStream<Path> directoryStream = 
                     Files.newDirectoryStream(userPath)){
            directoryStream.forEach(path -> {
                Path file = Paths.get(path.getFileName().toString());

                if(pathMatcher.matches(file)){
                    out.println(file.getFileName());
                }
            });
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值