UML软件工程组织
|
Java 2标准版 1.4的新I/O功能 |
John Zukowski著 来源:Sun |
回到2000年1月,那时许多人还在讨论2000年是世纪元年还是世纪末年,作为经过 认可的一个Java Specification Request(JSR,Java规范请求),JSR 51诞生了。这 个JSR的名称是用于Java平台的新I/O API。许多人以为新的功能只 是提供非独占式I/O操作。实际上,引入到Java TM 2平台标准版(J2SE)1.4 Beta版的新特性, 包括许多其他新的有趣的特点。这些API除了肯定对套接字和文件都支持可伸缩的I/O 操作之外,还包括用于模式匹配的正则表达式软件包,用于字符集转换的编码和解码 器,以及改进的文件系统支持,比如文件锁定和内存映射。这四项新特性在本文中都 会介绍。 注意: Java本地接口(JNI)为了支持新的I/O操作所作的变更不在本文论述之列。 有关这些变动的信息,请参见本文结尾的 资源一节 缓存从最简单的开始,最复杂的放在最后,首先要介绍的改进是
CharBuffer buff = CharBuffer.wrap(args[0]); for (int i=0, n=buff.length(); i<n; i++) { System.out.println(buff.get()); } 在使用缓存时,必须认识到大小和位置值存在差异,这一点要小心。 CharBuffer buff = CharBuffer.wrap(args[0]); for (int i=0; buff.length() > 0; i++) { System.out.println(buff.get()); } 回到不同的大小和位置值,这四个值称为标记、指针、边界和容量:
在对缓存进行读写时,指针是必须时刻关心的重要信息。例如,如果想把刚写入 的内容读出来,必须把指针移动到开始读取的地方,否则读取时就会超出边界,得 到的结果没有意义。这时 buff.put('a'); buff.flip(); buff.get(); 上面演示的 映射文件直接的 下面是从文件创建读-写 String filename = ...; FileInputStream input = new FileInputStream(filename); FileChannel channel = input.getChannel(); int fileLength = (int)channel.size(); MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); 你可以从 创建 // ISO-8859-1 is ISO Latin Alphabet #1 Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(buffer); 这些类可以在 正则表达式把输入文件映射到
注意:很不幸,当处理字符序列的任何对象查看字符缓存时(这对正则表达式而 言是必须的),J2SE 1.4的beta 3版会出错。有关这个问题的描述请参见 问题列表 。更不幸的是,这意味着你不能用模板匹配器来每次读取一个单词或一 行。 有关正则表达式库的其他信息,请参见 资源一节中引用的文章正则表达式和Java编程语言。 套接字通道介绍完文件通道,我们再来看读出和写入套接字连接的通道。这些通道可以以独占 或非独占的方式使用。在独占方式下,只需把调用改成 用于处理基本的套接字读写的新类是: 用 String host = ...; InetSocketAddress socketAddress = new InetSocketAddress(host, 80); 拥有 SocketChannel channel = SocketChannel.open(); channel.connect(socketAddress); 连上之后,就可以用 Charset charset = Charset.forName("ISO-8859-1"); CharsetEncoder encoder = charset.newEncoder(); String request = "GET / \r\n\r\n"; channel.write(encoder.encode(CharBuffer.wrap(request))); 然后就可以从这个通道读入响应了。因为这个HTTP请求的响应是文本,所以还需要 通过 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); while ((channel.read(buffer)) != -1) { buffer.flip(); decoder.decode(buffer, charBuffer, false); charBuffer.flip(); System.out.println(charBuffer); buffer.clear(); charBuffer.clear(); } 下面的程序把所有这些代码端连接起来,通过一个HTTP请求读取某个Web站点的主 页。可以放心地把输出保存到一个文件,以便将结果与用浏览器查看该页面相比较。 import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; public class ReadURL { public static void main(String args[]) { String host = args[0]; SocketChannel channel = null; try { // Setup InetSocketAddress socketAddress = new InetSocketAddress(host, 80); Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); // Allocate buffers ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); // Connect channel = SocketChannel.open(); channel.connect(socketAddress); // Send request String request = "GET / \r\n\r\n"; channel.write(encoder.encode(CharBuffer.wrap(request))); // Read response while ((channel.read(buffer)) != -1) { buffer.flip(); // Decode buffer decoder.decode(buffer, charBuffer, false); // Display charBuffer.flip(); System.out.println(charBuffer); buffer.clear(); charBuffer.clear(); } } catch (UnknownHostException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } finally { if (channel != null) { try { channel.close(); } catch (IOException ignored) { } } } } } 非独占式读取现在到了有趣的部分,也就是人们最关心的新I/O软件包。你怎么配置非独占式 通道连接的?基本步骤是在打开的 String host = ...; InetSocketAddress socketAddress = new InetSocketAddress(host, 80); channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(socketAddress); 有了非独占式通道,还必须考虑如何实际使用这个通道。 为了获得一个 Selector selector = Selector.open(); 通过通道的 channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); 到此为止,你只有等着选择器通知你,感兴趣的事件何时在已注册的通道上发生。 一旦感兴趣的事件发生,你必须决定发生何事,并据此做出反应。对于这里用选择 器注册的通道,你已表示对 下面是至此为止的循环基本框架: while (selector.select(500) > 0) { // Get set of ready objects Set readyKeys = selector.selectedKeys(); Iterator readyItor = readyKeys.iterator(); // Walk through set while (readyItor.hasNext()) { // Get key from set SelectionKey key = (SelectionKey)readyItor.next(); // Remove current entry readyItor.remove(); // Get channel SocketChannel keyChannel = (SocketChannel)key.channel(); if (key.isConnectable()) { } else if (key.isReadable()) { } } }
对于这个程序范例,你正在做的事等价于读取一个HTTP连接,所以除了连接,还 需要发送初始化的HTTP请求。基本上,一旦知道连接已建立,给该站点的root发送一 个GET请求就可以了。当选择器报告说该通道可以连接时,也许连接工作尚未完成。 所以,你应该通过 下面是连接代码: // OUTSIDE WHILE LOOP Charset charset = Charset.forName("ISO-8859-1"); CharsetEncoder encoder = charset.newEncoder(); // INSIDE if (channel.isConnectable()) // Finish connection if (keyChannel.isConnectionPending()) { keyChannel.finishConnect(); } // Send request String request = "GET / \r\n\r\n"; keyChannel.write (encoder.encode(CharBuffer.wrap(request))); 读套接字通道类似于读文件通道。除了一点例外。在读取套接字时,缓存好像 不会满似的。这并不重要,因为你要读的是已经准备好的内容。 // OUTSIDE WHILE LOOP CharsetDecoder decoder = charset.newDecoder(); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); // INSIDE if (channel.isReadable()) // Read what's ready in response keyChannel.read(buffer); buffer.flip(); // Decode buffer decoder.decode(buffer, charBuffer, false); // Display charBuffer.flip(); System.out.print(charBuffer); // Clear for next pass buffer.clear(); charBuffer.clear(); 加入必要的异常处理代码后,你就有了一个自己的套接字阅读器。一定要在 import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; import java.util.*; public class NonBlockingReadURL { static Selector selector; public static void main(String args[]) { String host = args[0]; SocketChannel channel = null; try { // Setup InetSocketAddress socketAddress = new InetSocketAddress(host, 80); Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); // Allocate buffers ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); // Connect channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(socketAddress); // Open Selector selector = Selector.open(); // Register interest in when connection channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); // Wait for something of interest to happen while (selector.select(500) > 0) { // Get set of ready objects Set readyKeys = selector.selectedKeys(); Iterator readyItor = readyKeys.iterator(); // Walk through set while (readyItor.hasNext()) { // Get key from set SelectionKey key = (SelectionKey)readyItor.next(); // Remove current entry readyItor.remove(); // Get channel SocketChannel keyChannel = (SocketChannel)key.channel(); if (key.isConnectable()) { // Finish connection if (keyChannel.isConnectionPending()) { keyChannel.finishConnect(); } // Send request String request = "GET / \r\n\r\n"; keyChannel.write(encoder.encode( CharBuffer.wrap(request))); } else if (key.isReadable()) { // Read what's ready in response keyChannel.read(buffer); buffer.flip(); // Decode buffer decoder.decode(buffer, charBuffer, false); // Display charBuffer.flip(); System.out.print(charBuffer); // Clear for next pass buffer.clear(); charBuffer.clear(); } else { System.err.println("Ooops"); } } } } catch (UnknownHostException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } finally { if (channel != null) { try { channel.close(); } catch (IOException ignored) { } } } System.out.println(); } } 非独占式服务器最后一段是让Web服务器使用NIO软件包。有了新的I/O能力,你可以创建一个不需 要每个连接建立一个线程的Web服务器。当然,对于长时间处理的任务,可以将线程 放入队列,但你要做的一切只是 使用通道的服务器的基本设置包括:调用 ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); InetSocketAddress isa = new InetSocketAddress(port); channel.socket().bind(isa); 其余事情几乎与客户机的读操作相同,只不过这次需要注册 以下代码范例只不过演示了这有多简单。它就是你的基本单线程服务器,对每个请 求送回一段预先录入的文本消息。只需用 import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.*; public class Server { private static int port = 9999; public static void main(String args[]) throws Exception { Selector selector = Selector.open(); ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); InetSocketAddress isa = new InetSocketAddress(port); channel.socket().bind(isa); // Register interest in when connection channel.register(selector, SelectionKey.OP_ACCEPT); // Wait for something of interest to happen while (selector.select() > 0) { // Get set of ready objects Set readyKeys = selector.selectedKeys(); Iterator readyItor = readyKeys.iterator(); // Walk through set while (readyItor.hasNext()) { // Get key from set SelectionKey key = (SelectionKey)readyItor.next(); // Remove current entry readyItor.remove(); if (key.isAcceptable()) { // Get channel ServerSocketChannel keyChannel = (ServerSocketChannel)key.channel(); // Get server socket ServerSocket serverSocket = keyChannel.socket(); // Accept request Socket socket = serverSocket.accept(); // Return canned message PrintWriter out = new PrintWriter (socket.getOutputStream(), true); out.println("Hello, NIO"); out.close(); } else { System.err.println("Ooops"); } } } // Never ends } } 收到请求之后,从套接字获得通道,使其变为非独占,还是用选择器注册该通道。 这个框架只提供了Web服务器内NIO类的基本用法。有关创建多线程服务器的其他信息, 请参见 资源一节中引用的JavaWorld文章。 结论J2SE 1.4 Beta版引入的新I/O特性,提供了用以提高程序性能的激动人心的新途 径。通过利用新功能的优点,程序不仅会更快,可缩放性也会大得多,因为你不必再 为每个连接建立一个线程这样的工作操心。在服务器端这一点尤为重要,它极大地提 升了可支持同时连接所可能达到的数量。 注意: 如果你看到JSR 51中的功能列表,就会发现里面提到支持扫描和格式化, 类似于C的 资源
关于作者John Zukowski在 JZ Ventures公司领导战略性Java咨询工作 他的最新著作是即将出版的 Java Collections和 Definitive Guide to Swing for Java 2(第二版)。请期待2002年出版的 Learn Java with JBuilder 6吧。John的联系方式是 mailto:jaz@zukowski.net?Subject=New I/O Article。. |
版权所有:UML软件工程组织 |