在JDK 1.7 版本中对NIO进行了完善,推出了NIO.2,也称为AIO(异步IO),在处理大量并发请求时具有优势,特别是在网络编程和高并发场景下,表现得更为出色。
对于输出流和输入流而言,操作的对象可以是文件,大多数情况下是文件,也可以是网络连接,当然也可以是内存数据。我们今天主要讨论的是文件和目录处理。
本博客我们将探讨NIO.2中最基础的Path、Paths和Files的用法。
Path 和 Files 是 Java NIO 中的两个核心类,分别代表文件系统中的路径和一组文件操作。
一、Path和Paths
(一)、File和Path的关系
早期的java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。可以表示文件或者文件目录。
Path 接口属于 java.nio 包中的最基础类,Path可以看做是java.io.File的升级版本。对 Path 接口而言,主要是为了在文件系统中定位文件的对象。通常情况下表示系统相关的文件路径。文件路径既包含文件也包含目录。
所以 Path 接口对象,可以是由一系列的目录和文件名元素组成的。并且是有一个有层次结构的路径。并且在文件路径中,还包含特殊的分隔符或定界符分割。当然也可以包含文件系统中的根目录。或者叫做盘符。对于文件而言,通常在文件路径中的最右侧。也就是最后一个名称中。其他的都是只能被称为目录。也就是说,目录中可以包含目录和文件。但是文件中却不能包含目录。
java.nio.file 包定义了访问文件和文件系统的类。
java.nio.file.attribute 包中定义是关于文件和文件系统的属性的 API 。
文件操作的二种常见写法:
在Java7以前文件的IO操作都是这样写的:
import java.io.File;
File file = new File(“test.txt”);
但在Java7 中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get(“test.txt”);
如果 Path 的路径由一个空的名字元素组成,则 Path 被认为是空路径。使用空路径访问文件相当于访问文件系统的默认目录。
Path常用方法:
boolean endsWith(String path) : 判断是否以 path 路径结束
boolean startsWith(String path) : 判断是否以 path 路径开始
boolean isAbsolute() : 判断是否是绝对路径
Path getFileName() : 返回与调用 Path 对象关联的文件名
Path getName(int idx) : 返回的指定索引位置 idx 的路径名称
int getNameCount() : 返回Path 根目录后面元素的数量
Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot() :返回调用 Path 对象的根路径
Path resolve(Path p) :将相对路径解析为绝对路径
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
String toString() : 返回调用 Path 对象的字符串表示形式
(二)、Path 对象的获取:
Path是接口,不能通过new关键字直接去构建 实例,创建实例大致有四种方法:
1)根据字符串格式的路径获取:使用Paths工具类中的get方法去获取,方法的参数是1个或1个以上的String,Paths会自动根据参数中的字符串对路径做拼接。另外,两个点"…"表示的是路径向上回退一级。
2)根据URI获取:使用Paths的get(URI url)方法获取。
3)根据File的实例进行转换:File中定义了实例方法toPath(),可以将File实例转换为Path的实例。
4)从对其他Path实例的操作中获取:这个相关的方法就有很多了,例如Path自己的getParent()、normalize()等。
JDK 1.7的NIO.2还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;
(三)、Paths的两个静态方法
Paths则包含了两个返回Path的静态工厂方法get() 方法用来获取 Path 对象:
static Path get(String first, String … more) : 用于将多个字符串串连成路径
static Path get(URI uri): 返回指定uri对应的Path路径
URI
资源网络地址URL:在互联网上,每一信息资源都有统一的网上地址,该地址称为URL(Uniform Resource Locator,统一资源定位器),它是互联网的统一资源定位标志,俗称“网络地址”。
URL是URI的一个子集,一般用于表示互联网上的资源网络地址。
URI可以直接作为参数传递给get()方法生成Path实例。
用URL转换成URI,再获取Path实例的用法:
URI resource = URI.create(“file:///Users/exampledir/examplefile.txt”);
Path path = Paths.get(resource.toURI());
请看实例,演示Paths这两个Path静态工厂方法get() 的用法:
package nio;import java.nio.file.Path;
import java.nio.file.Paths;
import java.net.URI;public class PathGetExample {public static void main(String[] args) {// 创建一个URI对象,指向文件系统中的某个路径//URI uri = URI.create("file:///Users/exampleuser/exampledir/examplefile.txt");URI uri = URI.create("file:/D:/Temp/image/c.txt");// 使用Path接口的get(URI uri)方法获取Path对象Path path = Paths.get(uri); //用法一// 打印获取到的Path对象System.out.println("Path: " + path.toString());path = Paths.get("D:/Temp/image/c.txt"); //用法二// 打印获取到的Path对象System.out.println("Path: " + path.toString());}
}
对于文件的访问, 通常Path和 Files 类一起使用。
(三)、Files类常用方法
java.nio.file.Files 用于操作文件或目录的工具类。
Files常用方法:用于创建、复制、删除和移动
Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
void delete(Path path) : 删除一个文件
Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
long size(Path path) : 返回 path 指定文件的大小
Files常用方法:用于文件读写
byte[] Files.readAllBytes(Path path):读取所有字节。
Path write(Path path, byte[] bytes):写入字节数组。
Files提供的简便方法适用于处理中等长度的文本文件
如果要处理的文件长度较大,或者二进制文件,那么还是应该使用经典的IO流或FileChannel来处理。
我们来看一个文件读写的演示程序:
package nio;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class ReadWriteFile {public static void main(String[] args) throws Exception {Charset charset = Charset.defaultCharset(); //获取系统环境的默认字符集名System.out.println( charset ); //显示系统环境默认字符集名称/*Files提供的简便方法适用于处理中等长度的文本文件如果要处理的文件长度较大,或者二进制文件,那么还是应该使用经典的IO流*/Path src = Paths.get("D:/temp/test.txt");Path target = Paths.get("D:/temp/target.txt");//将文件所有内容读入byte数组中byte[] bytes = Files.readAllBytes(src); //传入Path对象//字节数组可以根据字符集构建字符串String content = new String(bytes, charset);System.out.println(content); //显示文件内容//也可以直接当作行序列读入List<String> lines = Files.readAllLines(src, charset);//相反,也可以写一个字符串到文件中,默认是覆盖Files.write(target, content.getBytes(charset)); //传入byte[]//追加内容,根据参数决定追加等功能Files.write(target, content.getBytes(charset), StandardOpenOption.APPEND); //传入枚举对象,打开追加开关//将一个行String的集合List lines写出到文件中//Files.write(path, lines);}}
我的演示测试结果:
最后,文件target.txt内容如下,是test.txt内容的二倍,因为又追加多写了一遍。
Files常用方法:用于判断各种属性
boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
boolean isExecutable(Path path) : 判断是否是可执行文件
boolean isHidden(Path path) : 判断是否是隐藏文件
boolean isReadable(Path path) : 判断文件是否可读
boolean isWritable(Path path) : 判断文件是否可写
boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
请看一个测试例程,演示Files中一些常用方法:
package nio;
import static java.nio.file.StandardCopyOption.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;public class FilesTest {public static void copyFile(Path source, Path target)//复制{try {//path1需要在物理磁盘上真实存在,path2不存在则自动创建、存在则替换原有文件Files.copy(source,target, REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}public static void moveFile(Path source, Path target) //移动{try {//source需要在物理磁盘上真实存在Files.move(source,target,ATOMIC_MOVE); //移动是原子操作} catch (IOException e) {e.printStackTrace();}}public static boolean fileExists(Path path)//文件是否存在{boolean b=Files.exists(path, LinkOption.NOFOLLOW_LINKS);System.out.println("文件是否存在? "+b);return b;}public static void main(String[] args) {Path path1=Paths.get("D:/temp/a.txt");Path path2=Paths.get("D:/temp/b.txt");Path path3=Paths.get("D:/temp/c.txt");copyFile(path1, path2);moveFile(path2, path3);fileExists(path1);}}
Files常用方法: 用于操作目录和文件内容
SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
请看一个例程,演示访问文件:
package nio;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import static java.nio.file.StandardOpenOption.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FilesDemo {/****打开或创建文件,返回可访问的字节通道以访问该文件打开一个目录,返回一个DirectoryStream以遍历目录中的所有条目打开一个文件,返回输入流以从文件中读取打开或创建文件,返回可用于向文件写入字节的输出流***/public static void main(String[] args) {Path path1=Paths.get("D:/temp/a.txt");Path path2=Paths.get("D:/temp");Path path4=Paths.get("D:/temp/c.txt");SeekableByteChannel seekChannel=null;DirectoryStream<Path> ds =null;InputStream inStream = null;OutputStream os =null;try {//1、打开或创建文件,返回可访问的字节通道以访问该文件//READ:表示对应的Channel是可读的//WRITE:表示对应的Channel是可写的//CREATE:如果要写出的文件不存在则创建;存在则忽略//CREATE_NEW:如果要写出的文件不存在则创建;存在则抛出异常//static SeekableByteChannel newByteChannel(Path path, OpenOption... options)seekChannel=Files.newByteChannel(path1, READ,WRITE,CREATE);//2、打开一个目录,返回一个DirectoryStream以遍历目录中的所有条目//public static DirectoryStream<Path> newDirectoryStream(Path dir) throws IOExceptionds=Files.newDirectoryStream(path2);Iterator<Path> it=ds.iterator();while(it.hasNext()){System.out.println(it.next());}//3、打开一个文件,返回输入流以从文件中读取inStream = Files.newInputStream(path1,READ);//4、打开或创建文件,返回可用于向文件写入字节的输出流os=Files.newOutputStream(path4,WRITE);} catch (IOException e) {e.printStackTrace();}finally {try {if (seekChannel!=null)seekChannel.close();} catch (IOException e) {e.printStackTrace();}}}
}
(四)、Files的两种方法可进行目录的遍历操作
Files可获得可迭代的目录流进行遍历操作
1. 方式一
使用Files.newDirectoryStream。这个接口只打印第一层的文件和文件夹。它是采用File.listFiles来列举所有的第一层文件。另外,因为是打开了一个stream,需要在finally中close stream。比较好的是用户可以自己定义过滤条件,支持“*”等通配符。
使用这个方式Files可获得可迭代的目录流,传入一个目录Path,遍历子孙目录返回一个目录Path的Stream,注意这里所有涉及的Path都是目录而不是文件!
因此,Files类设计了newDirectoryStream(Path dir)及其重载方法,将生成Iterable对象(可用foreach迭代)。可用于遍历目录文件集合。它有三种重载形式:
1,static DirectoryStream
2,static DirectoryStream
3,static DirectoryStream
返回一个目录流 时,它内含一个实现了Iterable接口的目录信息的集合,
因此可用迭代器或foreach迭代,只是使用迭代器的时候要注意不能再递归调用另一个Iterator迭代器。
可以传入通配符glob参数,使用通配模式来过滤文件,注意glob参数是String类型。下面是样例代码:
try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir, "*.java")) //{...}
通配模式(glob模式)
所谓的通配模式是指可使用“正则表达式”来进行匹配。
1.星号 * 匹配路径组成部分包含0个或多个字符;例如 .java 匹配当前目录中的所有Java(扩展名为java)文件。
2.双星号 ** 匹配跨目录边界包含0个或多个字符;例如 **.java 匹配在所有子目录中的Java文件。
3.问号(?) 只匹配一个字符;例如 ???.java 匹配所有四个字符的Java文件,不包括扩展名。
4.[…] 匹配一个字符集合,可以用连线 [0-9] 和取反符 [!0-9];例如 Test[0-9A-F].java 匹配Testx.java,假设x是一个十六进制数字,[0-9A-F]是匹配单个字符为十六进制数字,比如B(十六进制不区分大小写)。
如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
5.{…} 匹配大括号内由逗号隔开的多个可选项之中的一个;例如 .{java,class} 可匹配所有Java文件或类class文件。
6.\ 转义上述任意模式中的字符;例如 * 匹配所有子目录中文件名包含的文件,这里为 ** 转义,前面第一个“”是匹配0个或多个字符。
网友总结的通配模式的规则:
请看一个打印当前目录中文件和目录的例子:
只能打印当前目录中的文件和目录。
package nio;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PrintDirectory {/**** 打印当前目录中的文件和目录* @param rootPath* @throws IOException* ***/public static void printFileDirectory(String rootPath) throws IOException {Path path = Paths.get(rootPath);if (Files.notExists(path)) {String msg = "目录不存在:"+path;System.out.println(msg);return;}File[] listFiles = path.toFile().listFiles();for (File file : listFiles) {System.out.println("文件 :" + file.getAbsolutePath());}}/*** 打印当前目录中的文件和目录(用通配模式打印)* @param rootPath* @param glob* @throws IOException*/public static void printDirectory(String rootPath, String glob) throws IOException {Path path = Paths.get(rootPath);if (Files.notExists(path)) {String msg = "目录不存在:"+path;System.out.println(msg);return;}DirectoryStream<Path> newDirectoryStream = null;try {if (glob != null) {newDirectoryStream = Files.newDirectoryStream(path, glob);} else {newDirectoryStream = Files.newDirectoryStream(path);}for (Path directory : newDirectoryStream) {System.out.println("路径:" + directory.toString());}} finally {if (newDirectoryStream != null) {newDirectoryStream.close();}}}public static void main(String[] args) throws Exception {String rootPath = "D:/temp";String glob = "*.mp3";printDirectory(rootPath, glob);System.out.println("分隔线--------------------------------------");printFileDirectory(rootPath);}
}
测试结果图:
2. 方式二
使用Files.walkFileTree接口。这个接口遍历目录下所有的文件和文件夹。接口的参数需要传入FileVisitor,提供了文件、文件夹的操作接口。
FileVisitor接口介绍
第二个参数需要实现FileVisitor接口,重写这个接口的四个方法:preVisitDirectory、visitFile、visitFileFailed、postVisitDirectory。
这几个方法的返回值都是枚举类型:
CONTINUE:继续;
SKIP_SIBLINGS:继续遍历,不过忽略当前节点的兄弟节点,直接返回上一层继续遍历
SKIP_SUBTREE:继续遍历,忽略后代目录,但继续访问子文件
TERMINATE:终止遍历
下面是一个演示程序:调用Files类的walkFileTree方法,并传入一个FileVisitor接口类型的对象。可递归打印指定目录下的所有文件和目录信息。
package nio;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
public class WalkFileTreeDemo {public static void printFileTree(String rootPath) throws IOException {Path path = Paths.get(rootPath);if (Files.notExists(path)) {String msg = "目录不存在:"+rootPath;System.out.println(msg);return;}Files.walkFileTree(path, new FileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path path, BasicFileAttributes arg1) throws IOException {System.out.println(path.toString());return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult postVisitDirectory(Path arg0, IOException arg1) throws IOException {System.out.println("向后遍历目录:" + arg0.toString());return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult preVisitDirectory(Path arg0, BasicFileAttributes arg1) throws IOException {System.out.println("向前遍历目录:" + arg0.toString());return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path arg0, IOException arg1) throws IOException {return FileVisitResult.CONTINUE;}});}public static void main(String[] args) throws Exception {String rootPath = "D:/temp";printFileTree(rootPath);}}
测试结果图:
参考文献:
Java NIO Path接口和Files类配合操作文件