Path相关接口JDK7加入NIO的,在java.nio.file包中。JDK7的NIO更新主要包含两个方面:
- 新增Path接口,Paths工具类,Files工具类。 这些接口和工具类对NIO中的功能进行了高度封装,大大简化了文件系统的IO编程。
- 基于异步Channel的IO,新增了多个Aysnchronous开头的channel接口和类。
在NIO基础上改进的IO也被称为NIO.2
1.Path接口
在java.io及java.nio中,是通过File类来访问文件系统(文件和目录都用File类表示),但是File类不能利用特定文件系统的特性,且性能不高,抛出的异常也太抽象,因此在NIO.2中引入了Path接口。
Path接口表示的是一个与平台无关的路径(文件和目录都用Path表示)。
Path既可以是绝对路径也可以是相对路径。
绝对路径包含文件系统根目录到指向具体文件或目录的完整路径;
相对路径包含一个文件或目录到其他位置的路径。
Path接口在很多方面和java.io.File类很想,但还是有一些细微的差异,在很多场景下是可以用Path替换File使用的。
1.1.创建Path
在使用Path之前需要创建Path对象,可以使用Paths工具类的静态方法进行创建
Path path = Paths.get("C:\\DATA\\test.txt");
Paths.get()是一个创建Path的工厂方法,需要传入一个绝对路径作为参数。
上边我们传入的是Windows系统的文件路径格式,在Unix系统(Linux,MacOS…)中我们也可以传入下边的路径
Path path = Paths.get("/home/data/test.txt");
根据相对路径创建Path
相对路径从一个基础路径指向其他文件或者目录,这个文件或者目录的路径由基础路径和相对路径共同组成。相对路径可以通过下边方法创建
// 目录相对路径
Path projects = Paths.get("d:\\data", "projects");
// 文件相对路径
Path file = Paths.get("d:\\data", "projects\\file.txt");
相对路径中特殊符号
- . 表示当前目录
- .. 表示上级目录
Path currentDir = Paths.get(".");
System.out.print(currentDir.toAbsolutePath());
Path parentDir = Paths.get("..");
System.out.print(parentDir.toAbsolutePath());
1.2.Path.normalize()
normalize()方法可以标准化路径,它会处理路径中的相对路径,去除“.”“..”
Path path = Paths.get("c:/Z_DATA/./test.txt");
System.out.println("path = " + path);
path = path.normalize();
System.out.println("path = " + path);
// 输出
path = c:\Z_DATA\.\test.txt
path = c:\Z_DATA\test.txt
这里需要注意 “path = path.normalize();”normalize()并不会改变原Path实例,它会返回一个新的Path对象,这里直接将返回值赋值给path变量了。
2.Files
Java NIO2中增加了Files工具类封装提供了一些操作文件系统文件的工具方法。下面介绍一些常用的方法的使用;Files类是和Path一起使用的,在使用Files方法前需要先理解Path接口。
2.1. Files.exists()
检查一个Path在文件系统中是否存在,你可能创建了一个文件系统中不存在的Path对象。例如你希望新建一个目录,你需要先创建一个对应的Path对象,然后创建目录。Path指向的文件或目录可能存在也可能不存在,所以你需要Files.exists()进行判断。
boolean exists = Files.exists(path, new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
方法的第二个参数是一个包含LinkOption.NOFOLLOW_LINKS的数组,它的意思是Files.exists()方法不需要跟踪文件系统中的象征性连接判断文件是否存在。
2.2. Files.createDirctory()
创建目录
Path newDir = Paths.get("c:/Z_DATA/newDir");
try {
if(!Files.exists(newDir)) {
Files.createDirectory(newDir);
}
} catch (IOException e) {
e.printStackTrace();
}
在调用创建方法前最好先检查是否存在,如果已经存在会抛出 FileAlreadyExistsException异常。
2.3. Files.copy()
copy()方法从一个路径拷贝文件到另一个路径
Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
Files.copy(sourcePath, targetPath);
} catch (IOException e) {
e.printStackTrace();
}
copy方法只能复制到不存在的路径,如果复制的目标文件已存在则会抛出异常。强制覆盖已存在文件也是可以的,需要增加相应参数:
Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); // 复制并覆盖已有文件
} catch (IOException e) {
e.printStackTrace();
}
2.4 Files.move()
Java NIO Files类同样提供了移动文件的方法。
Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); // 移动并覆盖已有文件
} catch (IOException e) {
e.printStackTrace();
}
2.5 Files.delete()
删除文件或目录
Files.delete(targetPath);
如果删除的文件或目录不存在会抛出IOException异常。
2.6 Files.walkFileTree() 遍历文件
walkFileTree()包含用于递归遍历目录树的方法,方法参数接收一个Path对象和一个FileVisitor对象,Path对象指向需要遍历的目录,FileVisitor在便利的时候调用。FileVistor接口定义如下:
public interface FileVisitor {
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException;
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException;
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException;
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
}
使用的时候需要实现FileVisitor的接口
try {
Files.walkFileTree(start, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("pre visit dir:" + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("visit file:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.out.println("visit file failed:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("post visit dir:" + dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
输出:
pre visit dir:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data
visit file:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data\nio-data-to.txt
visit file:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data\nio-data.txt
post visit dir:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data
FileVisitor接口中的方法会在不同的时间被调用:
- preVisitDirectory():在访问任意目录前调用;
- postVisitDirectory():在访问任意目录完成后调用;
- visitFile():在访问到每个文件时调用;
- visitFileFaild():在访问文件失败是调用
这四个方法都会返回一个FileVistResult枚举类型的实例,FileVisitResult枚举包含四个选项:
- CONTINUE:继续遍历;
- TERMINATE:立即终止遍历;
- SKIP_SIBLINGS:跳过当前目录下的兄弟文件继续遍历;
- SKIP_SUBTREE:跳过当前目录继续遍历,这个只能在preVisitDirectory()方法中返回,在其他方法中返回和CONTINUE效果一样。
使用walkFileTree实现文件检索
下面展示一个使用walkFileTree和继承SimpleFileVistor查找名为nio-data.txt 文件的例子
try {
Files.walkFileTree(start, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String filePath = file.toAbsolutePath().toString();
if(filePath.endsWith("nio-data.txt")){
System.out.println("file found at path:" + filePath);
return FileVisitResult.TERMINATE;
}
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
e.printStackTrace();
}
使用walkFileTree递归删除目录
walkFileTree还可以用来递归删除目录及其目录中所有文件,Files.delete()只能删除空目录或者文件,当目中包含文件的时候无法直接删除,只能删除目录下所有文件后才能删除目录,walkFileTreee可以在visitFile()方法中删除文件,然后在postVisitDirectory()删除目录实现递归删除目录:
try {
Files.walkFileTree(start, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("delete file:" + file);
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("delete dir:" + dir);
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
3.AsynchronousFileChannel
AsynchronousFileChannel也是jdk7加入NIO的,它可以异步的读写文件数据。下面介绍如何使用AsynchronousFileChannel.
3.1.创建AsynchronousFileChannel
AsynchronousFileChannel asyncFileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ);
AsynchronousFileChannel是一个抽象类,只能通过它的静态方法open()创建实例,需要传入文件路径参数和打开模式。
3.2读数据
AsynchronousFileChannel读取数据有两种方式,每种方式都会调用一种read()方法:
- 通过Future读取数据
这一种读方法返回一个Future对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> operation = asyncFileChannel.read(buffer, 0);
这种read()方法的重载ByteBuffer作为第一个参数,数据从channel中读取到buffer中,第二个参数是读取的起始位置。
read()方法会立即返回,即使读操作还没有完成。我们可以使用read()方法返回的Future对象的isDone()方法检查操作是否完成。
下边是使用这种读方法的完整例子
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> operation = asyncFileChannel.read(buffer, position);
while (!operation.isDone()){}
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();
- 通过CompletionHandler读取数据
第二种read()方法使用一个ComplationHandler作为参数:
asyncFileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println(result);
attachment.flip();
byte[] data = new byte[attachment.limit()];
attachment.get(data);
System.out.println(new String(data));
attachment.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
}
});
一旦读操作完成,ConpletionHandler的completed()方法就会被调用,completed()方法的第一个参数是一个整数告诉我们有多少个字节被读取了,第二个参数“attachment”是read()方法的第三个参数,是已经读入数据的buffer。当操作失败的时候回调用failed()方法。
3.3. 写数据
和读数据一样,写数据也有两种方法
- 基于Future写数据
// 写模式打开 AsynchronousFileChannel
AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.WRITE);
position = 12;
buffer.put("test data".getBytes());
buffer.flip();
Future<Integer> writeOperation = writeFileChannel.write(buffer, position);
buffer.clear();
while (!writeOperation.isDone()){}
System.out.println("write done");
- 基于CompletionHandler写数据
writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("bytes written:" + result);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("bytes written failed");
}
});
写操作完成,ConpletionHandler的completed()方法就会被调用。