Java:前程似锦的 NIO 2.0

Java 之因此可以霸占编程语言的榜首,其强大、丰富的类库功不可没,几乎全部的编程问题都能在其中找到解决方案。但在早期的版本当中,输入输出(I/O)流并不那么令开发者感到愉快:css


1)JDK 1.4 以前的 I/O 没有缓冲区的概念、不支持正则表达式、支持的字符集编码有限等等;java

2)JDK 1.4 的时候引入了非阻塞 I/O,也就是 NIO 1.0,但遍历目录很困难,不支持文件系统的非阻塞操做等等。nginx


为了突破这些限制,JDK 1.7 的时候引入了新的 NIO,也就是本篇文章的主角——NIO 2.0。git


0一、基石:Path程序员


Path 既能够表示一个目录,也能够表示一个文件,就像 File 那样——固然了,Path 就是用来取代 File 的。web


1)能够经过 Paths.get() 建立一个 Path 对象,此时 Path 并无真正在物理磁盘上建立;参数既能够是一个文件名,也能够是一个目录名;绝对路径或者相对路径都可。面试


2)能够经过 Files.notExists() 确认 Path(目录或者文件) 是否已经存在。正则表达式


3)能够经过 Files.createDirectory() 建立目录,此时目录已经在物理磁盘上建立成功,可经过资源管理器查看到。算法


4)能够经过 Files.createFile() 建立文件,此时文件已经在物理磁盘上建立成功,可经过资源管理器查看到。编程


5)能够经过 toAbsolutePath() 查看 Path 的绝对路径。


6)能够经过 resolve() 将 Path 链接起来,参数能够是一个新的 Path 对象,也能够是对应的字符串。


具体的代码以下:


public class Wanger {
public static void main(String[] args) { // 相对路径 Path dir = Paths.get("chenmo");
// 输出 dir 的绝对路径 System.out.println(dir.toAbsolutePath()); // 输出:D:\program\java.git\java_demo\chenmo
if (Files.notExists(dir)) { try { // 若是目录不存在,则建立目录 Files.createDirectory(dir); } catch (IOException e1) { e1.printStackTrace(); } } // 这时候 chenmo.txt 文件并未建立 // 经过 resolve 方法把 dir 和 chenmo.txt 连接起来 Path file = dir.resolve("chenmo.txt");
// 输出 file 的绝对路径 System.out.println(file.toAbsolutePath()); // 输出:D:\program\java.git\java_demo\chenmo\chenmo.txt
if (Files.notExists(file)) { try { // 若是文件不存在,则建立文件 Files.createFile(file); } catch (IOException e) { e.printStackTrace(); } }
}
}


若是要将 File 转换为 Path,能够经过 File 类的 toPath() 方法完成。代码示例以下:


File file = new File("沉默王二.txt");Path path = file.toPath();


若是要将 Path 转换为 File,能够经过 Path 类的 toFile() 方法完成。代码示例以下:


Path path = Paths.get("沉默王二.txt");File file = path.toFile();


0二、处理目录


NIO 2.0 新增的 java.nio.file.DirectoryStream<T> 接口能够很是方便地查找目录中的(符合某种规则的)文件,好比说咱们要查找 chenmo 目录下的 txt 后缀的文件,代码示例以下:


// 相对路径Path dir = Paths.get("chenmo");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) { for (Path entry : stream) { System.out.println(entry.getFileName()); }} catch (IOException e) { e.printStackTrace();}


1)Files.newDirectoryStream(Path dir, String glob) 会返回一个过滤后的 DirectoryStream( 目录流,),第一个参数为目录,第二个参数为 glob 表达式,好比 *.txt 表示全部 txt 后缀的文件。


2)因为 DirectoryStream 继承了 Closeable 接口,因此它能够配合 try-with-resources 语法写出更安全的代码,目录流会自动调用 close 方法关闭流,释放与流相关的资源,不须要再经过 finally 进行主动关闭。


3)DirectoryStream 被称为目录流,容许方便地使用 for-each 结构来遍历目录。


0三、处理目录树


目录树意味着一个目录里既有文件也有子目录,也可能都没有,也可能有其一。NIO 2.0 能够很方便地遍历一颗目录树,并操做符合条件的文件;这其中关键的一个方法就是 Files 类的 walkFileTree,其定义以下:


public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException{ return walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, visitor); }


第二个参数 FileVisitor 被称为文件访问器接口,它实现起来很是复杂,要实现 5 个方法呢,但幸亏 JDK 的设计者提供了一个默认的实现类 SimpleFileVisitor,若是咱们只想从目录树中找到 txt 后缀的文件,能够这样作:


// 相对路径Path dir = Paths.get("chenmo");
try { Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.toString().endsWith(".txt")) { System.out.println(file.getFileName()); } return FileVisitResult.CONTINUE; } });} catch (IOException e) { e.printStackTrace();}


经过建立匿名内部类来重写 SimpleFileVisitor 的 visitFile 方法,若是后缀名为 txt 就打印出来。


0四、文件的删除、复制、移动


建立一个文件很是的简单,以前咱们已经体验过了,那么删除一个文件也一样的简单,代码示例以下:


Files.delete(file);Files.deleteIfExists(file);


使用 Files.delete() 删除文件以前最好使用 Files.exists() 判断文件是否存在,不然会抛出 NoSuchFileException;Files.deleteIfExists() 则不用。


复制文件也不复杂,代码示例以下:


Path source = Paths.get("沉默王二.txt");Path target = Paths.get("沉默王二1.txt");Files.copy(source, target);


移动文件和复制文件很是类似,代码示例以下:


Path source = Paths.get("沉默王二.txt");Path target = Paths.get("沉默王二1.txt");Files.move(source, target);


0五、快速地读写文件


NIO 2.0 提供了带有缓冲区的读写辅助方法,使用起来也很是的简单。能够经过 Files.newBufferedWriter() 获取一个文件缓冲输入流,并经过 write() 方法写入数据;而后经过 Files.newBufferedReader() 获取一个文件缓冲输出流,经过 readLine() 方法读出数据。代码示例以下。


Path file = Paths.get("沉默王二.txt");
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { writer.write("一个有趣的程序员");} catch (Exception e) { e.printStackTrace();}
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line; while ((line = reader.readLine()) != null) { System.out.println(line); }} catch (Exception e) { e.printStackTrace();}


 0六、重要:异步 I/O 操做


实话实说吧,上面提到的那些都算是 NIO 2.0 的甜点,而异步 I/O 操做(也称 AIO)才算是真正重要的内容。异步 I/O 操做能够充分利用多核 CPU 的特色,不须要再像之前那样启动一个线程来对 I/O 进行处理,省得阻塞了主线程的其余操做。


异步 I/O 操做的核心概念是发起非阻塞方式的 I/O 操做,当 I/O 操做完成时通知。能够分为两种形式:Future 和 Callback。若是咱们但愿主线程发起 I/O 操做并轮循等待结果时,通常使用 Future 的形式;而 Callback 的基本思想是主线程派出一个侦查员(CompletionHandler)到独立的线程中执行 I/O 操做,操做完成后,会触发侦查员的 completed 或者 failed 方法。


1)Future 


先来看一个示例,代码以下:


public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { Path file = Paths.get("沉默王二.txt"); AsynchronousFileChannel channel = AsynchronousFileChannel.open(file); Future<Integer> result = channel.read(ByteBuffer.allocate(100_000), 0); while (!result.isDone()) { System.out.println("主线程继续作事情"); }
Integer bytesRead = result.get(); System.out.println(bytesRead);}



1)经过 AsynchronousFileChannel.open() 打开一个异步文件通道 channel。


2)用 Future 来保存从通道中读取的结果。


3)经过 isDone() 轮循判断异步 I/O 操做是否完成,若是没有完成的话,主线程能够继续作本身的事情。


2)Callback


先来看一个示例,代码以下:


public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { Path file = Paths.get("沉默王二.txt"); AsynchronousFileChannel channel = AsynchronousFileChannel.open(file); channel.read(ByteBuffer.allocate(100_000), 0, null, new CompletionHandler<Integer, ByteBuffer>() { public void completed(Integer result, ByteBuffer attachment) { System.out.println(result); }  public void failed(Throwable exc, ByteBuffer attachment) { System.out.println(exc.getMessage()); } });  System.out.println("主线程继续作事情");
}



1)经过 AsynchronousFileChannel.open() 打开一个异步文件通道 channel。


2)在 read 方法中使用匿名内部类的形式启用 CompletionHandler,而后实现 CompletionHandler 的两个监听方法,completed 的时候打印结果,failed 的时候打印异常信息。


不论是 Future 形式仍是 Callback 形式,总之异步 I/O 是一个强大的特性,能够保证在处理大文件时性能不受到显著的影响。



往期精彩回顾:

JVM虚拟机系列

JVM内存结构和垃圾回收算法(一)

内存结构和垃圾回收算法(二)

老年代的垃圾回收算法(三)

于加入知识星球的同窗提供基本的福利:

文章有疑问的地方能够提问,其余工做问题均可以提问出来,做者免费做答。

 https://t.zsxq.com/Y3fYny7


每周都有大牛分享一些面试题,和面试注意的知识点!

 https://t.zsxq.com/2bufE2v


每周由Java极客技术独家编制的设计模式与你们分享!

 https://t.zsxq.com/3bUNbEI


每两周还会分享一个话题,和你们一块儿成长!

 https://t.zsxq.com/BI6Unm2


还有Java极客技术团队亲自录制了一套 Spring Boot 视频,这套视频加密,加密后放到云盘上,下载连接加密以后,一机一码,每一个星球的用户一个播放受权码。

 

咱们作知识星球的目的和其余星主同样,就是为了帮助你们一块儿更好的成长,与高手拉近距离,减小差距,其实你也是高手!

1000人,50元/每一年,如今大约还剩487名额。

长按二维码

隆重介绍:

Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专一分享原创、高质量的 Java 文章。若是您以为咱们的文章还不错,请帮忙赞扬、在看、转发支持,鼓励咱们分享出更好的文章。


本文分享自微信公众号 - Java极客技术(Javageektech)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索