Java NIO学习系列七:Path、Files、AsynchronousFileChannel
相对于标准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 wronge.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 wronge.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 wronge.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 failede.printStackTrace(); }
Path path = Paths.get("data/subdir/logging-moved.properties"); try {Files.delete(path); } catch (IOException e) {//deleting file failede.printStackTrace(); }
该方法删除目录时只能删除空目录,如果想删除下面有文件的目录则需要进行递归删除,后面会介绍。
2.6 Files.walkFileTree()
该方法能够递归地获取目录树,该方法接收两个参数,一个是指向目标目录,另一个是一个FileVisitor类型对象:
Files.walkFileTree(path, new FileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println("pre visit dir:" + dir);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println("visit file: " + file);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {System.out.println("visit file failed: " + file);return FileVisitResult.CONTINUE;}@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println("delete file: " + file.toString());Files.delete(file);return FileVisitResult.CONTINUE;}@Overridepublic 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>() {@Overridepublic 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();}@Overridepublic 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>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("bytes written: " + result);}@Overridepublic 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相关推荐
- Java NIO学习系列六:Java中的IO模型
前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...
- Java NIO学习系列四:NIO和IO对比
前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...
- Java NIO学习系列三:Selector
前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...
- Java NIO学习系列二:Channel
上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...
- Java NIO学习系列一:Buffer
从本文开始我会开始总结NIO部分,Java NIO(注意,这里的NIO其实叫New IO)是用来替换标准Java IO以及Java 网络API的,其提供了一系列不同与标准IO API的方式来处理IO, ...
- Java NIO学习系列五:I/O模型
前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...
- Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/Pipe
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- [初级]Java命令学习系列(七)——javap
转载自 [初级]Java命令学习系列(七)--javap javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码. 一般情况下,很少有人使用javap对class文件 ...
- Java NIO 学习笔记(三)----Selector
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
最新文章
- 重要更新 | 谷歌发布 TensorFlow 1.4,迁移Keras,支持分布式训练
- 项目管理project甘特图模板_【八大项目管理应用】必用项目管理工具对比推荐...
- 不管服不服 Windows仍是全球第一大桌面系统
- 分分钟一键部署Zabbix Server
- UITableView-FDTemplateLayoutCell自动计算UITableView高度的使用
- 【机器学习】 - 关于图像质量评价IQA(Image Quality Assessment)
- ubuntu下rar文件解压后文件名乱码
- Android超级好看的动态登陆界面(附有详细代码)
- 如何 设置CTEX WinEdt 改变默认的 PDF viewer
- QT图形显示和处理5
- 百度网盘里的html怎么用,百度网盘怎么用?
- 无线抄表火热,ZigBee暂输于专用协议[转]
- 三星S21 FE 参数配置 三星S21 FE评测
- 用于 LLM 应用开发的 LangChain 中文版
- 软件工程毕业设计课题(83)微信小程序毕业设计PHP家政服务预约小程序系统设计与实现
- 贝塞尔曲线工具css,贝塞尔曲线以及css动画 | Soo Smart!
- 2021 Java 这一年
- 树莓派人脸识别_童话树莓派|(十一)进入AI世界人脸识别(防疫检测)
- 熊猫阿波的故事及C++保留小数点的问题
- java-php-python-springboot网上订餐系统计算机毕业设计
热门文章
- oracle pde文件导入
- oracle时间格式转换问题 ORA-01810: format code appears twice--转
- 【Python】Pyecharts数据可视化模块练习
- 【采用】【科技金融】互金风控大数据盘点
- 图片相似度识别_deepface:人脸识别\特征分析
- 如何一夜暴富?深度学习教你预测比特币价格
- 无约束最优化方法-牛顿法
- 白话Elasticsearch60-数据建模实战_Join datatype 父子关系数据建模
- 实战SSM_O2O商铺_39【前端展示】首页轮播图和一级商铺Dao+Service+Controller层的实现
- MyBatis-17MyBatis代码生成器(逆向工程)MBG使用