相对于标准Java IO中通过File来指向文件和目录,Java NIO中提供了更丰富的类来支持对文件和目录的操作,不仅仅支持更多操作,还支持诸如异步读写等特性,本文我们就来学习一些Java NIO提供的和文件相关的类:

  Java NIO Path

  Java NIO Files

  Java NIO AsynchronousFileChannel

  总结

1. Java NIO Path

  Java Path是一个接口,位于java.nio.file包中,Java 7中引入到Java NIO中。

  一个Java Path实现的实例对象代表文件系统中的一个路径,指向文件和目录,(标准Java IO中是通过File来指向文件和路径的),以绝对路径或者相对路径的方式。

  java.nio.file.Path接口很多方面类似于java.io.File类,但是两者之间也是有细微的差别的。在大多数场景下是可以用Path来代替File的。

1.1 创建Path实例对象

  可以通过Paths类的静态工厂方法get()来创建一个Path实例对象:

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");
}
}

1.2 Creating an Absolute Path

  通过直接指定绝对路径可以创建使用绝对路径方式指向文件的Path:

// windows系统
Path path = Paths.get("c:\\data\\myfile.txt"); // linux系统
Path path = Paths.get("/home/jakobjenkov/myfile.txt");

1.3 Creating a Relative Path

  通过如下方式可以创建使用相对路径方式指向文件的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

2. Java NIO Files

  Java NIO Files类(java.nio.file.Files)提供了一些方法用来操作文件,其是和上面提到的Path一起配合使用的。

2.1 Files.exists()

  该方法可以用来检查Path指向的文件是否真实存在,直接看例子:

Path path = Paths.get("data/logging.properties");

boolean pathExists = Files.exists(path, new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});

2.2 Files.createDirectory()

  该方法会在硬盘上创建一个新的目录(即文件夹):

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();
}

2.3 Files.copy()

  该方法会将文件从一个地方复制到另一个地方:

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();
}

2.4 Files.move()

  该方法能够移动文件,也可以实现重命名的效果:

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();
}

2.5 Files.delete()

  该方法能够删除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();
}

  该方法删除目录时只能删除空目录,如果想删除下面有文件的目录则需要进行递归删除,后面会介绍。

2.6 Files.walkFileTree()

  该方法能够递归地获取目录树,该方法接收两个参数,一个是指向目标目录,另一个是一个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个方法,分别在目录转换的四个不同阶段调用:

  • preVisitDirectory()方法在访问目录之前调用,而postVisitorDirectory()方法是在访问目录之后调用;
  • visitFile()方法会在访问每个文件(访问目录是不会调用的)时调用一次,而visitorFileFailed()会在访问文件失败时被调用,比如没有访问权限或者别的问题。

  这四个方法都会返回一个FileVisitResult枚举对象,包含如下成员:

  • CONTINUE
  • TERMINATE
  • SKIP_SIBLINGS
  • SKIP_SUBTREE

  被调用的如上四个方法通过这些返回值来判断是否要继续遍历目录。

  • CONTINUE,意味着继续;
  • TERMINATE,意味着终止;
  • SKIP_SIBLINGS,意味着继续,但是不再访问该文件或目录的兄弟;
  • SKIP_SUBTREE,意味着继续,但是不再访问该目录下的条目。只有preVisitDirectory()返回该值才有意义,其余三个方法返回则会当做CONTINUE处理;

  如果不想自己实现该接口,也可以使用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来实现类似功能我们还需要自己处理整个遍历的过程。

2.7 其它有用方法

  java.nio.file.Files类还包含了很多别的有用方法,比如创建符号链接、文件大小、设置文件权限,这里就不一一介绍了,有兴趣的可以参考Java官方文档。

3. Java NIO AsynchronousFileChannel

  Java 7中引入了AsynchronousFileChannel,使得可以异步地读写数据到文件。

3.1 Creating an AsynchronousFileChannel

  通过其静态方法可以创建一个AsynchronousFileChannel。

Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

  第一个参数是一个指向要和AsynchronousFileChannel关联的文件的Path实例。第二个参数代表要对文件指向的操作,这里我们指定StandardOpenOption.READ,意思是执行读操作。

3.2 Reading Data

  从AsynchronousFileChannel读数据有两种方式:

通过Future读数据

  第一种方式是调用一个返回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读数据

  第二种读数据的方式是调用其包含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()方法。

3.3 Writing Data

  与读类似,写数据也支持两种方式。

通过Future写

  如下是一个写数据的完整例子:

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写数据

  可以借助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()方法。

4. 总结

  本文总结了Java NIO中提供的对文件操作的相关类:Path、Files、AsynchronousFileChannel。

  Path是一个接口,其实现实例可以指代一个文件或目录,作用与Java IO中的File类似。Path接口很多方面类似于java.io.File类,但是两者之间也是有细微的差别的,不过在大多数场景下是可以用Path来代替File的。

  Files是一个类,提供了很多方法用来操作文件,是和上面提到的Path一起配合使用的,Files提供的对文件的操作功能要多于File。

  AsynchronousFileChannel是Channel的子类,提供了异步读取文件的能力。

Java NIO学习系列七:Path、Files、AsynchronousFileChannel的更多相关文章

  1. JAVA NIO学习四:Path&Paths&Files 学习

    今天我们将学习NIO 的最后一章,前面大部分涉及IO 和 NIO 的知识都已经讲过了,那么本章将要讲解的是关于Path 以及Paths 和 Files 相关的知识点,以对前面知识点的补充,好了言归正传 ...

  2. Java NIO学习系列六:Java中的IO模型

    前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...

  3. Java NIO学习系列一:Buffer

    前面三篇文章中分别总结了标准Java IO系统中的File.RandomAccessFile.I/O流系统,对于I/O系统从其继承体系入手,力求对类数量繁多的的I/O系统有一个清晰的认识,然后结合一些 ...

  4. Java NIO学习系列三:Selector

    前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...

  5. Java NIO学习系列四:NIO和IO对比

    前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...

  6. Java NIO学习笔记七 Non-blocking Server

    Java NIO:Non-blocking Server 即使你了解了Java NIO非阻塞功能的工作(怎么样Selector,Channel, Buffer等等),设计一个无阻塞服务器仍然很难.非阻 ...

  7. Java NIO学习系列二:Channel

    上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...

  8. Java NIO学习系列五:I/O模型

    前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...

  9. Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/Pipe

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

随机推荐

  1. 开源系统监控工具Nagios、Zabbix和Open-Falcon的功能特性汇总及优缺点比较

    Nagios Nagios 全名为(Nagios Ain’t Goona Insist on Saintood),最初项目名字是 NetSaint.它是一款免费的开源 IT 基础设施监控系统,其功能强 ...

  2. 201907 TIOBE 编程语言排行榜-Python坐稳第三

    目录 一.编程语言7月排行榜 二.Top10编程语言指数走势(2002-2018) 三.历史排名(1988-2019) 四.编程语言"名人榜"( 2003-2018) 五.Top2 ...

  3. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  4. kuangbin专题 专题二 搜索进阶 Nightmare Ⅱ HDU - 3085

    题目链接:https://vjudge.net/problem/HDU-3085 题意:有两个鬼和两个人和墙,鬼先走,人再走,鬼每走过的地方都会复制一个新鬼, 但新鬼只能等待旧鬼走完一次行程之后,下一 ...

  5. dubbo webservice 区别

    DUBBO中可以设置采用webservice方式,进行数据交互. 随着交互系统的增多,这种方式对系统的侵入性越来越大,关系更为错综复杂,很容易出错. 较适用与外围系统通信,若是内部系统间则会出现以上较 ...

  6. 源代码扫描工具Fortify SCA与FindBugs的简单对比

    前段时间因为工作原因需要对java源代码进行扫描,现结合使用经验对静态代码扫描工具Fortify SCA与FindBugs进行一个简单的对比. 一.Fortify SCA Fortify SCA是由全 ...

  7. 满足高密度设备存储需求 一颗ICMAXLPDDR4X 8GB就行

    通讯技术在当代无疑更新越来越快速,随着5G时代即将到来,对通讯的设备的要求也将提出更高的要求.具备AI功能的硬件设备将普及化,其智能化程度也将越来越高,同时对手机等智能移动设备的内存容量和带宽也提出了 ...

  8. python数据库-MongoDB的基本使用(54)

    一.MongoDB 创建数据库 语法:MongoDB 创建数据库的语法格式如下: use DATABASE_NAME 如果数据库不存在,则创建数据库,否则切换到指定数据库. > use Hero ...

  9. Communicating with the UI Thread_翻译

    In the previous lesson you learned how to start a task on a thread managed by ThreadPoolExecutor. Th ...

  10. Ray聊天记录

    由于工作变动,Ray的文档.示例没有及时更新,深表歉意.在Ray升级后,性能较几个月前有了非常大的提升,也更具易用性.这是QQ交流群里大家的聊天记录,跟大家分享一下(由于时间仓促群里大家的聊天记录没有 ...