相对于标准Java IO中经过File来指向文件和目录,Java NIO中提供了更丰富的类来支持对文件和目录的操做,不只仅支持更多操做,还支持诸如异步读写等特性,本文咱们就来学习一些Java NIO提供的和文件相关的类:java
Java NIO Pathlinux
Java NIO Fileswindows
Java NIO AsynchronousFileChannel异步
总结async
Java Path是一个接口,位于java.nio.file包中,Java 7中引入到Java NIO中。ide
一个Java Path实现的实例对象表明文件系统中的一个路径,指向文件和目录,(标准Java IO中是经过File来指向文件和路径的),以绝对路径或者相对路径的方式。post
java.nio.file.Path接口不少方面相似于java.io.File类,可是二者之间也是有细微的差异的。在大多数场景下是能够用Path来代替File的。学习
能够经过Paths类的静态工厂方法get()来建立一个Path实例对象:spa
import java.nio.file.Path; import java.nio.file.Paths; public class PathExample { public static void main(String[] args) { Path path = Paths.get("c:\\data\\myfile.txt"); } }
经过直接指定绝对路径能够建立使用绝对路径方式指向文件的Path:线程
// windows系统 Path path = Paths.get("c:\\data\\myfile.txt"); // linux系统 Path path = Paths.get("/home/jakobjenkov/myfile.txt");
经过以下方式能够建立使用相对路径方式指向文件的Path:
Path projects = Paths.get("d:\\data", "projects");
Path file = Paths.get("d:\\data", "projects\\a-project\\myfile.txt");
采用相对路径的方式时,有两个符号能够用来表示路径:
“.”能够表示当前目录,以下例子是打印当前目录(即应用程序的根目录):
Path currentDir = Paths.get(".");
System.out.println(currentDir.toAbsolutePath());
".."表示父文件夹。
当路径中包含如上两种符号时,能够经过调用normalize()方法来将路径规范化:
String originalPath = "d:\\data\\projects\\a-project\\..\\another-project"; Path path1 = Paths.get(originalPath); System.out.println("path1 = " + path1); Path path2 = path1.normalize(); System.out.println("path2 = " + path2);
输出结果以下:
path1 = d:\data\projects\a-project\..\another-project
path2 = d:\data\projects\another-project
Java NIO Files类(java.nio.file.Files)提供了一些方法用来操做文件,其是和上面提到的Path一块儿配合使用的。
该方法能够用来检查Path指向的文件是否真实存在,直接看例子:
Path path = Paths.get("data/logging.properties"); boolean pathExists = Files.exists(path, new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});
该方法会在硬盘上建立一个新的目录(即文件夹):
Path path = Paths.get("data/subdir"); try { Path newDir = Files.createDirectory(path); } catch(FileAlreadyExistsException e){ // the directory already exists. } catch (IOException e) { //something else went wrong e.printStackTrace(); }
该方法会将文件从一个地方复制到另外一个地方:
Path sourcePath = Paths.get("data/logging.properties"); Path destinationPath = Paths.get("data/logging-copy.properties"); try { Files.copy(sourcePath, destinationPath); } catch(FileAlreadyExistsException e) { //destination file already exists } catch (IOException e) { //something else went wrong e.printStackTrace(); }
若是目标文件已存在,这里会抛出java.nio.file.FileAlreadyExistsException异常,想要强制覆盖文件也是能够的:
Path sourcePath = Paths.get("data/logging.properties"); Path destinationPath = Paths.get("data/logging-copy.properties"); try { Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING); } catch(FileAlreadyExistsException e) { //destination file already exists } catch (IOException e) { //something else went wrong e.printStackTrace(); }
该方法可以移动文件,也能够实现重命名的效果:
Path sourcePath = Paths.get("data/logging-copy.properties"); Path destinationPath = Paths.get("data/subdir/logging-moved.properties"); try { Files.move(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { //moving file failed. e.printStackTrace(); }
该方法可以删除Path实例指向的文件或目录:
Path path = Paths.get("data/subdir/logging-moved.properties"); try { Files.delete(path); } catch (IOException e) { //deleting file failed e.printStackTrace(); }
Path path = Paths.get("data/subdir/logging-moved.properties"); try { Files.delete(path); } catch (IOException e) { //deleting file failed e.printStackTrace(); }
该方法删除目录时只能删除空目录,若是想删除下面有文件的目录则须要进行递归删除,后面会介绍。
该方法可以递归地获取目录树,该方法接收两个参数,一个是指向目标目录,另外一个是一个FileVisitor类型对象:
Files.walkFileTree(path, 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 directory: " + dir); return FileVisitResult.CONTINUE; } });
FileVisitor是一个接口,你须要实现它,接口的定义以下:
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 { }
该接口中包含4个方法,分别在目录转换的四个不一样阶段调用:
这四个方法都会返回一个FileVisitResult枚举对象,包含以下成员:
被调用的如上四个方法经过这些返回值来判断是否要继续遍历目录。
若是不想本身实现该接口,也可使用SimpleFileVisitor,这是一个默认实现,以下是一个利用SimpleFileVisitor来实现文件查找、删除的例子:
Path rootPath = Paths.get("data"); String fileToFind = File.separator + "README.txt"; try { Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String fileString = file.toAbsolutePath().toString(); if(fileString.endsWith(fileToFind)){ System.out.println("file found at path: " + file.toAbsolutePath()); return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } }); } catch(IOException e){ e.printStackTrace(); }
由于delete()方法只能删除空目录,对于非空目录则须要将其进行遍历以逐个删除其子目录或文件,能够经过walkFileTree()来实现,在visitFile()方法中删除子目录,而在postVisitDirectory()方法中删除该目录自己:
Path rootPath = Paths.get("data/to-delete"); try { Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("delete file: " + file.toString()); Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); System.out.println("delete dir: " + dir.toString()); return FileVisitResult.CONTINUE; } }); } catch(IOException e){ e.printStackTrace(); }
其实利用walkFileTree()方法,咱们能够很轻松地指定本身的逻辑,而无需考虑是如何遍历的,若是要用标准Java IO提供的File来实现相似功能咱们还须要本身处理整个遍历的过程。
java.nio.file.Files类还包含了不少别的有用方法,好比建立符号连接、文件大小、设置文件权限,这里就不一一介绍了,有兴趣的能够参考Java官方文档。
Java 7中引入了AsynchronousFileChannel,使得能够异步地读写数据到文件。
经过其静态方法能够建立一个AsynchronousFileChannel。
Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
第一个参数是一个指向要和AsynchronousFileChannel关联的文件的Path实例。第二个参数表明要对文件指向的操做,这里咱们指定StandardOpenOption.READ,意思是执行读操做。
从AsynchronousFileChannel读数据有两种方式:
第一种方式是调用一个返回Future的read()方法:
Future<Integer> operation = fileChannel.read(buffer, 0);
这个版本的read()方法,其第一个参数是一个ByteBuffer,数据从channel中读到buffer中;第二个参数是要从文件中开始读取的字节位置。
该方法会立刻返回,即便读操做实际上尚未完成。经过调用Future的isDone()方法能够知道读操做是否完成了。
以下是一个更详细的例子:
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; Future<Integer> operation = fileChannel.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();
在这个例子中,当调用了AsynchronousFileChannel的read()方法以后,进入循环直到Future对象的isDone()返回true。固然这种方式并无有效利用CPU,只是由于本例中须要等到读操做完成,其实这个等待过程咱们可让线程作别的事情。
第二种读数据的方式是调用其包含CompletionHandler参数的read()方法:
fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("result = " + 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) { } });
当读操做完成以后会调用ComplementHandler的completed()方法,该方法的第一个入参是一个整型变量,表明读了多少字节数据,第二个入参是一个ByteBuffer,保存着已经读取的数据。
若是读失败了,则会调用ComplementHandler的fail()方法。
与读相似,写数据也支持两种方式。
以下是一个写数据的完整例子:
Path path = Paths.get("data/test-write.txt"); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; buffer.put("test data".getBytes()); buffer.flip(); Future<Integer> operation = fileChannel.write(buffer, position); buffer.clear(); while(!operation.isDone()); System.out.println("Write done");
过程比较简单,就不讲一遍了。这个例子中有一个问题须要注意,文件必须事先准备好,若是不存在文件则会抛出java.nio.file.NoSuchFileException异常。
能够经过以下方式判断文件是否存在:
if(!Files.exists(path)){ Files.createFile(path); }
能够借助CompletionHandler来通知写操做已经完成,示例以下:
Path path = Paths.get("data/test-write.txt"); if(!Files.exists(path)){ Files.createFile(path); } AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; buffer.put("test data".getBytes()); buffer.flip(); fileChannel.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("Write failed"); exc.printStackTrace(); } }); System.out.println(“异步执行哦”);
如上是一个异步写入数据的例子,为了演示效果,我特地在 调用write方法以后打印了一行日志,运行结果以下:
异步执行哦
bytes written: 9
说明调用write方法并无阻塞,而是继续往下执行,因此先打印日志,而后数据写好以后回调completed()方法。
本文总结了Java NIO中提供的对文件操做的相关类:Path、Files、AsynchronousFileChannel。
Path是一个接口,其实现实例能够指代一个文件或目录,做用与Java IO中的File相似。Path接口不少方面相似于java.io.File类,可是二者之间也是有细微的差异的,不过在大多数场景下是能够用Path来代替File的。
Files是一个类,提供了不少方法用来操做文件,是和上面提到的Path一块儿配合使用的,Files提供的对文件的操做功能要多于File。
AsynchronousFileChannel是Channel的子类,提供了异步读取文件的能力。