目录
NIO.2概述
NIO.2在JDK1.7中发布,针对原有的文件IO操作进行了优化及封装,并支持Asynchronous IO。从Java IO详细总结(源码解析)、Java NIO详细总结(源码解析)这两篇文章中可以看到在针对文件进行操作时不管是IO还是NIO都需要搭配File,通过File来跟OS关联。而在NIO.2中围绕着文件系统提出了三大概念FileSystem,Path,Symbolic Links也是本文的核心内容,下面将逐一介绍。需要注意的是本文跟Java IO详细总结(源码解析)、Java NIO详细总结(源码解析)一样,只讨论文件IO至于网络IO会在其他文章中展开。
文件系统
在开始介绍NIO.2之前先来了解下什么是文件系统(File System)。文件系统在某种形式的媒体(通常是一个或多个硬盘)上存储和组织文件,以便于检索。目前使用的大多数文件系统都将文件存储在树(或层次结构)结构中。树的顶部是一个(或多个)根节点。在根节点下,有文件和目录(Microsoft Windows中的文件夹)。每个目录都可以包含文件和子目录,而这些文件和子目录又可以包含文件和子目录,潜在的深度几乎是无限的。一般常见的就是Windows以及UNIX系统,下面是一个常见的文件系统结构图:

对于Windows系统来说有分区的概念,文件系统的根节点可以是任意一个分区,而在UNIX系统中文件系统都是从/开始。拿test来说在Windos中的文件路径就是C:\data\test,对于UNIX就是/data/test。NIO.2中通过类Path来表示文件系统中的路径,其中路径又分为绝对路径以及相对路径:
- 绝对路径:绝对路径一定带着根路径,例如/data/test就是一条完整的文件路径通过它可以找到对应的文件;
- 相对路径:相对路径不是完整的文件路径例如data/test,它需要跟根目录/配合成一条完整的路径才能够找到对应文件。
需要注意的是在Java中你可以随意构建一个Path实例,但是该对象对应的文件不一定存在于文件系统中,需要通过Files类来校验是否存在。
在文件系统中值得一提的还有符号链接(Symbolic Link),符号链接指的是在文件系统中文件可以指向其他文件,在这个过程中源文件只是起到一个指向性的作用(指向目标文件)即路径C:\data\test与C:\etc\test访问的都是同一个文件test2,如下图所示:

文件路径
通过类Paths提供的静态方法get可以拿到对应的Path实例对象(Path为接口,get方法拿到的是其实现类后上转成Path),下面来看看Path一些常见的用法:
// Path path = Paths.get("D:\\", "data\\test.txt"); 相对路径
Path path = Paths.get("D:\\data\\test.txt");// 绝对路径
System.out.println(Files.exists(path));
System.out.format("toString: %s%n", path.toString());
System.out.format("getFileName: %s%n", path.getFileName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %s%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
// 删除路径中多余的东西,例如.或者..
Path p1 = Paths.get("D:\\data\\.\\test.txt");
System.out.println("normalize . : " + p1.normalize());
p1 = Paths.get("D:\\data\\test\\.\\test.txt");
System.out.println("normalize test\\. : " + p1.normalize());
p1 = Paths.get("D:\\data\\test\\..\\test.txt");
System.out.println("normalize test\\.. : " + p1.normalize());
// 比较两个路径
System.out.println("compare path : " + path.equals(p1));
/* 输出
exists : false
toString: D:\data\test.txt
getFileName: test.txt
getName(0): data
getNameCount: 2
subpath(0,2): data\test.txt
getParent: D:\data
getRoot: D:\
normalize . : D:\data\test.txt
normalize test\. : D:\data\test\test.txt
normalize test\.. : D:\data\test.txt
compare path : false */文件及目录
NIO.2中通过Files类来操作文件以及目录,不需要实例化该对象其内部的所有方法都是静态的。值得一提的是Files的所有操作都是建立在Path之上的。
文件元数据
文件的元数据指是文件的信息,包括但不局限于修改时间、创建时间、访问权限、文件大小。Files支持检索单个元数据,也可以批量获取多个文件的元数据(多个元数据放到一个对象中),前者查了api文档就可以了解,下文将介绍下后者。
由于不同的OS有不同的文件系统,为此NIO.2针对这些不同分为成了6种视图:
- BasicFileAttributeView:提供所有文件系统实现所需支持的基本属性的视图;
- DosFileAttributeView:扩展基本属性视图,在支持DOS属性的文件系统上支持标准的四位;
- PosixFileAttributeView:扩展基本属性视图,在支持POSIX系列标准(如Unix)的文件系统上支持这些属性。这些属性包括文件所有者、组所有者和九个相关的访问权限;
- FileOwnerAttributeView:由支持文件所有者概念的任何文件系统实现支持;
- AclFileAttributeView:支持读取或更新文件的访问控制列表(acl)。支持NFSv4 ACL模型。也可以支持任何具有到NFSv4模型的定义良好的映射的ACL模型,如Windows ACL模型;
- UserDefinedFileAttributeView:支持用户定义的元数据。此视图可以映射到系统支持的任何扩展机制。例如,在Solaris操作系统中,可以使用此视图存储文件的mime类型。
在jdk中有两种方式可以批量获取文件的属性,一种是通过getFileAttributeView获取上述的6种视图;另一种是通readAttributes获取属性的关联对象。两种方法获取到的属性关联对象都是一样的,区别在于,前者支持修改文件的元数据。下面是一个简单的示例:
Path file = Paths.get("D:\\test.txt");
BasicFileAttributes attr;
BasicFileAttributeView bf = Files.getFileAttributeView(file, BasicFileAttributeView.class);
try {
attr = bf.readAttributes();
System.out.println("viewName : " + bf.name());
System.out.println("creationTime: " + attr.creationTime());
System.out.println("lastAccessTime: " + attr.lastAccessTime());
System.out.println("lastModifiedTime: " + attr.lastModifiedTime());
System.out.println("isDirectory: " + attr.isDirectory());
System.out.println("isOther: " + attr.isOther());
System.out.println("isRegularFile: " + attr.isRegularFile());
System.out.println("isSymbolicLink: " + attr.isSymbolicLink());
System.out.println("size: " + attr.size());
long currentTime = System.currentTimeMillis();
FileTime ft = FileTime.fromMillis(currentTime);
// 修改文件的最后修改时间
bf.setTimes(ft, null, null);
} catch (IOException e) {
e.printStackTrace();
}
/* // 另一种方式批量获取文件的元数据
try {
attr = Files.readAttributes(file, BasicFileAttributes.class);
} catch (IOException e) {
e.printStackTrace();
}
*/文件、目录校验
下面将介绍通过Files来校验文件或者目录是否存在、是否有相应的操作权限、比较两个Path指向的文件(注意跟Path的equals区分开,由于符号链接的存在,即使Path不同指向的文件可以相同)是否相同。
Path p1 = Paths.get("D:\\test.txt");
boolean exist = Files.exists(p1);
System.out.println(exist);
boolean notExists = Files.notExists(p1);
System.out.println(notExists);不管是exists还是noExists方法都可以指定LinkOption参数,通过该参数可以处理符号链接。此外,判断一个文件或者目录是否存在需要考虑下面三种情况:
- 如果exists返回true,则文件存在;
- 如果noExists返回true,则文件不存在;
- 如果exists以及noExists都返回false,文件可能不存在,也可能是无法访问。
通过api可以看到Files可以查看一个文件的多种操作权限,比较简单不再赘述。

创建、删除、复制、移动
Files中提供了创建文件以及目录的方法,其中根据不同的文件系统支持不同的特性:
Path path = Paths.get("D:\\test");
Path file = Paths.get("D:\\test\\test2.txt");
try {
// 如果是POSIX系列标准(如Unix)的文件系统,可以指定文件的访问权限
/* Set<PosixFilePermission> perms =
PosixFilePermissions.fromString("rwx------");
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(perms);*/
// 本例运行在Windows下
Files.createDirectory(path);
Files.createFile(file);
} catch (IOException e) {
e.printStackTrace();
}
由于例子比较简单不再赘述,下面介绍下复制、移动中的可选参数。
jdk中定义了枚举类StandardCopyOption来提供三种复制、移动过程中的可选参数:

文件、目录读写
在NIO.2Files类中可以选择使用字节流,字符流或者是通道的方式来读写文件。

跟StandardCopyOption一样jdk提供了枚举类StandardOpenOption来指定文件的操作方式:

字节流、字符流、通道等操作文件IO用法基本一致,不在赘述。
新旧File API比较
下面是原有java.io.File API与java.nio.File API中的对应关系:

参考文献
https://docs.oracle.com/javase/tutorial/essential/io/fileio.html