探秘JDK 7之四:下一代I/O(NIO.2)

开发 后端
本文为51CTO.com探秘JDK 7系列的最后一篇。在前三篇当中,我们详细介绍了JDK 7的语言特性等新特点,本文将介绍JDK 7中的下一代I/O(NIO.2)。

51CTO曾在Java 7 下一代Java开发技术详解专题里对“JDK 7 I/O新功能”有过简单地介绍,其实早在2000年的时候,Sun公司就启动了JSR 51:为Java平台开发新的I/O API,直接访问操作系统底层输入/输出操作以提高应用程序的性能,首次引入这套API是在J2SE 1.4中,根据维基百科的新I/O词条显示,新I/O(NIO)由下列API组成:

◆ 原始类型数据缓冲

◆ 字符集编码和解码

◆ 通道,新的原始I/O抽象

◆ 支持上锁和内存映射的文件接口,文件最大支持Integer.MAX_VALUE字节(2GB)

◆ 为可扩展服务器提供的多路复用,无阻塞I/O设施(基于选择器和键)

JSR 203(NIO.2)除了解决JSR 51遗留下来的问题外,还为Java平台提供了更多新的I/O API,NIO.2解决了java.awt.File文件系统接口存在的重大问题,引入了异步I/O,并完成了未包括在JSR 51中的功能,下面列出了包含在JSR 203中的主要组件:

◆ 新的文件系统接口,支持大块访问文件属性,更改通知,绕开文件系统指定的API,也是可插拔文件系统实现的服务提供者接口。

◆ 对套接字和文件同时提供了异步I/O操作的API。

◆ JSR 51中定义的完整的套接字通道功能,此外还包括绑定,选项配置和多播数据报的支持。

新的文件系统接口

Java的File类存在重大问题,例如,操作出错时,delete()和mkdir()方法返回一个状态码而不是一个异常,没有办法获知失败的原因,此外还包括以下问题:

◆ File没有提供方法来检测符号链接,要知道为什么检测符号链接很重要,以及如何解决这个问题的办法,请参考Patrick的文章“在Java中如何处理文件系统软链接/符号链接”和“Java中的链接/别名/快捷方式”。

◆ File提供的方法只能访问部分文件属性,不能访问文件权限和访问控制列表。

◆ File没有提供方法一次访问文件的所有属性(如文件的修改时间和它的类型),因为文件系统需要为每个属性执行查询请求,可能存在性能问题。

◆ File的list()和listFiles()方法返回文件名和目录名的数组,但不支持大目录,通过网络展示大目录清单时,调用list()/listFiles()方法可能会使当前的线程阻塞相当长一段时间,而在服务器端,虚拟机可能会耗尽内存。

◆ File没有提供复制和移动文件的方法,虽然File提供了一个renameTo()方法在某些时候可以用来移动文件,但它的行为与平台关系紧密,即在不同平台上的行为是不一致的,根据renameTo()的文档说明,这个方法不能在文件系统之间移动文件,它可能不是原子的,如果目标路径下已存在同名文件,这个操作可能不会成功。

◆ File也没有提供改变通知方法,需要应用程序自己实现,因此导致应用程序的性能下降,例如,服务器需要确定什么时候往目录中添加了一个新的JAR文件,它需要实时监视这个目录,因为服务器后台线程需要频繁读取文件系统,因此性能会有所下降。

◆ File也不允许开发人员引入他们自己的文件系统访问功能,例如,开发人员可能想将文件系统存储到一个zip文件中,或创建一个内存文件系统。

NIO.2引入了新的文件系统接口,除了解决上述存在的问题外,还引入了更多的功能,这个接口由位于java.nio.file,java.nio.file.attribute和java.nio.file.spi包中的类和其它类型组成。

这些包提供了多个切入点,其中一个切入点就是java.nio.file.Paths类,它提供了两个方法返回一个java.nio.file.Path实例:

◆ public static Path get(String path) – 它通过转换给定路径字符串返回给这个实例构造一个Path实例。

◆ public static Path get(URI uri) -它通过转换给定路径的URI(统一资源定位符)返回给这个实例构造一个Path实例。

与传统的基于File的代码互操作:

File类提供了一个public Path toPath()方法,它可以将一个File实例转换成一个Path实例。

当你创建了一个Path实例后,你就可以使用这个实例执行许多路径操作(如返回路径的一部分,连接两个路径)和许多文件操作(如删除,移动和复制文件)。

为了不将问题复杂化,我就不深入讲解Path了,这里我用一段代码简单地演示一下以前的get()方法和Path的delete()方法。

清单1. InformedDelete.java

  1. // InformedDelete.java  
  2. import java.io.IOException;  
  3. import java.nio.file.DirectoryNotEmptyException;  
  4. import java.nio.file.NoSuchFileException;  
  5. import java.nio.file.Path;  
  6. import java.nio.file.Paths;  
  7. public class InformedDelete  
  8. {  
  9.    public static void main (String [] args)  
  10.    {  
  11.       if (args.length != 1)  
  12.       {  
  13.           System.err.println ("usage: java InformedDelete path");  
  14.           return;  
  15.       }  
  16.       // Attempt to construct a Path instance by converting the path argument  
  17.       // string. If unsuccessful (you passed an empty string as the  
  18.       // command-line argument), the get() method throws an instance of the  
  19.       // unchecked java.nio.file.InvalidPathException class.  
  20.       Path path = Paths.get (args [0]);  
  21.       try  
  22.       {  
  23.           path.delete (); // Attempt to delete the path.  
  24.       }  
  25.       catch (NoSuchFileException e)  
  26.       {  
  27.           System.err.format ("%s: no such file or directory%n", path);  
  28.       }  
  29.       catch (DirectoryNotEmptyException e)  
  30.       {  
  31.           System.err.format ("%s: directory not empty%n", path);  
  32.       }  
  33.       catch (IOException e)  
  34.       {  
  35.           System.err.format ("%s: %s%n", path, e);  
  36.       }  
  37.    }  
  38. }  
  39.  

InformedDelete调用Path的delete()方法解决了File的delete()方法不能确定失败原因的问题,当Path的delete()当的检测到操作失败时,它会根据情况抛出适当的异常,如:

◆ 如果文件不存在,抛出java.nio.file.NoSuchFileException异常。

◆ 如果文件是一个目录不能删除,抛出java.nio.file.DirectoryNotEmptyException异常,因为这个目录下可能还包括一个空目录。

◆ 如果遇到其他I/O问题,则抛出java.io.IOException的子类异常,例如,如果文件是只读的,抛出java.nio.file.AccessDeniedException异常。

#p#

异步I/O

JSR 51引入了多路复用I/O(无阻塞I/O和选择就绪的结合)使创建高可扩展服务器变得更加容易,本质上是这样的,客户端代码用一个选择器注册一个套接字通道,当通道准备好可以开始I/O操作时发出通知。

如果要深入研究多路复用I/O,请阅读Ron Hitchens的《Java NIO》一书。

JSR 203还引入了异步I/O,它也被用来建立高可扩展服务器,和多路复用I/O不同,异步I/O是让客户端启动一个I/O操作,当操作完成后向客户端发送一个通知。

异步I/O是通过以下位于java.nio.channels包中的接口和类实现的,它们的名称前面都加了Asynchronous前缀:

◆ AsynchronousChannel – 标识一个支持异步I/O的通道。

◆ AsynchronousByteChannel – 标识一个支持读写字节的异步通道,这个接口扩展了AsynchronousChannel。

◆ AsynchronousDatagramChannel – 标识一个面向数据报套接字异步通道,这个类实现了AsynchronousByteChannel。

◆ AsynchronousFileChannel – 标识一个可读,写和操作文件的异步通道,这个类实现了AsynchronousChannel。

◆ AsynchronousServerSocketChannel – 标识一个面向流监听套接字的异步通道,这个类实现了AsynchronousChannel。

◆ AsynchronousSocketChannel – 标识一个面向流连接套接字的异步通道,这个类实现了AsynchronousByteChannel。

◆ AsynchronousChannelGroup – 标识一个用于资源共享的异步通道组。

AsynchronousChannel文档指定了两种形式的异步I/O操作:

◆ Future operation(...)

◆ void operation(... A attachment, CompletionHandler handler)

operation列举I/O操作(如读,写),V是操作的结果类型,A是附加给操作的对象类型。

第一种形式需要你调用java.util.concurrent.Future方法检查操作是否完成,等待完成和检索结果,清单2的代码演示了这样一个示例。

清单2. AFCDemo1.java

  1. // AFCDemo1.java  
  2. import java.io.IOException;  
  3. import java.nio.ByteBuffer;  
  4. import java.nio.channels.AsynchronousFileChannel;  
  5. import java.nio.file.Path;  
  6. import java.nio.file.Paths;  
  7. import java.util.concurrent.Future;  
  8. public class AFCDemo1  
  9. {  
  10.    public static void main (String [] args) throws Exception  
  11.    {  
  12.       if (args.length != 1)  
  13.       {  
  14.           System.err.println ("usage: java AFCDemo1 path");  
  15.           return;  
  16.       }  
  17.       Path path = Paths.get (args [0]);  
  18.       AsynchronousFileChannel ch = AsynchronousFileChannel.open (path);  
  19.       ByteBuffer buf = ByteBuffer.allocate (1024);  
  20.       Future<Integer> result = ch.read (buf, 0);  
  21.       while (!result.isDone ())  
  22.       {  
  23.          System.out.println ("Sleeping...");  
  24.          Thread.sleep (500);  
  25.       }  
  26.       System.out.println ("Finished = "+result.isDone ());  
  27.       System.out.println ("Bytes read = "+result.get ());  
  28.       ch.close ();  
  29.    }  

调用AsynchronousFileChannel's public static AsynchronousFileChannel open(Path file, OpenOption... options)方法打开file参数进行读取,然后创建了一个字节缓冲区存储读取操作的结果。

接下来调用public abstract Future read(ByteBuffer dst, long position)方法异步读取文件的前1024个字节,这个方法返回一个Future实例代表这个操作的结果。

调用read()方法后,进入一个表决循环,重复调用Future的isDone()方法检查操作是否完成,一直等到读操作结束,最后调用Future的get()方法返回读取到的字节大小。

第二种形式需要你指定java.nio.channels.CompletionHandler,并实现下面的方法使用前面操作返回的结果,或是了解操作为什么失败,并采取适当的行动:

◆ 当操作完成时调用void completed(V result, A attachment),这个操作的结果是由result标识的,附加给操作的对象是由attachment标识的。

◆ 当操作失败时调用void failed(Throwable exc, A attachment),操作失败的原因是由exc标识的,附加给操作的对象是由attachment标识的。

#p#

我创建了一个程序演示创建和接收读操作状态的通知,其代码如清单3所示。

清单3. AFCDemo2.java

  1. // AFCDemo2.java  
  2. import java.io.IOException;  
  3. import java.nio.ByteBuffer;  
  4. import java.nio.channels.AsynchronousFileChannel;  
  5. import java.nio.channels.CompletionHandler;  
  6. import java.nio.file.Path;  
  7. import java.nio.file.Paths;  
  8. public class AFCDemo2  
  9. {  
  10.    static Thread current;  
  11.    public static void main (String [] args) throws Exception  
  12.    {  
  13.       if (args.length != 1)  
  14.       {  
  15.           System.err.println ("usage: java AFCDemo1 path");  
  16.           return;  
  17.       }  
  18.       Path path = Paths.get (args [0]);  
  19.       AsynchronousFileChannel ch = AsynchronousFileChannel.open (path);  
  20.       ByteBuffer buf = ByteBuffer.allocate (1024);  
  21.       current = Thread.currentThread ();  
  22.       ch.read (buf, 0, null,  
  23.                new CompletionHandler<Integer, Void> ()  
  24.                {  
  25.                    public void completed (Integer result, Void v)  
  26.                    {  
  27.                       System.out.println ("Bytes read = "+result);  
  28.                       current.interrupt ();  
  29.                    }  
  30.                    public void failed (Throwable exc, Void v)  
  31.                    {  
  32.                       System.out.println ("Failure: "+exc.toString ());  
  33.                       current.interrupt ();  
  34.                    }  
  35.                });  
  36.       System.out.println ("Waiting for completion");  
  37.       try  
  38.       {  
  39.           current.join ();  
  40.       }  
  41.       catch (InterruptedException e)  
  42.       {  
  43.       }  
  44.       System.out.println ("Terminating");  
  45.       ch.close ();  
  46.    }  

上面的代码调用AsynchronousFileChannel's public abstract void read(ByteBuffer dst, long position, A attachment, CompletionHandler handler)方法异步读取前1024字节。

虽然我们只演示了单一的读操作,但attachment部分也很重要,上面的代码演示了传递一个null给read()方法,并指定附加类型为Void。

完整的套接字通道功能

JSR 51的DatagramChannel,ServerSocketChannel和SocketChannel类没有完整抽象一个网络套接字,为了绑定通道的套接字,或为了获得/设置套接字选项,你必须先调用每个类的socket()方法检索对等套接字。

JSR 51生效时没有时间定义完整的套接字通道API,因此形成了套接字通道和套接字API混合的局面,JSR203引入新的java.nio.channels.NetworkChannel接口解决了这个问题。

NetworkChannel提供了将套接字绑定到本地地址,返回绑定地址,以及获得/设置套接字选项的方法,这个接口是通过同步和异步套接字类实现的,不再需要调用socket()方法。

JSR 203也引入了新的java.nio.channels.MulticastChannel接口,它为DatagramChannel提供了IP多播的支持,以及对应的异步支持。

总结

本系列文章介绍了即将发布的JDK 7包含的一些新特性,新的里程碑版本可能很快就会发布,你现在就可以尝试一下这些新特性,也许Oracle/Sun将会增加更多的新特性,如JWebPane浏览器组件,因为之前Sun就曾用闭包让我们惊讶过一次了。

关于Java 7的更多内容,欢迎访问51CTO推荐专题:Java 7 下一代Java开发技术详解

【JDK 7相关内容推荐】

  1. 探秘JDK 7之三:JLayer装饰Swing组件
  2. 探秘JDK 7之二:半透明和任意形状的窗口
  3. 探秘JDK 7:将会出现新的语言特性
  4. Google技术演讲介绍Java 7 NIO.2概览
责任编辑:佚名 来源: IT168
相关推荐

2013-07-27 21:28:44

2013-06-27 11:21:17

2012-06-15 09:21:03

Windows 7Windows XP

2013-07-25 21:08:37

2020-09-27 17:27:58

边缘计算云计算技术

2022-05-12 13:15:11

谷歌AI模型

2016-01-26 11:58:12

2020-09-16 10:28:54

边缘计算云计算数据中心

2014-05-09 13:18:54

iOS移动互联网

2012-10-29 12:23:44

BYODIT

2011-06-30 11:02:22

2015-09-28 16:24:34

YARNHadoop计算

2018-09-25 07:00:50

2009-04-06 08:42:18

Firefox浏览器

2012-12-12 10:29:57

2009-01-11 10:13:39

Stripes开发框架JSP

2018-09-27 18:47:45

AIOpsDevOps

2013-07-27 21:41:14

APT攻击下一代威胁

2022-07-06 11:38:40

人工智能AI

2013-09-09 16:28:36

点赞
收藏

51CTO技术栈公众号