| 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软件工程组织 |