内容目录
3.1.2 使用通道
我们在第二章的学习中已经知道了,通道将数据传输给ByteBuffer
对象或者从ByteBuffer
对象获取数据进行传输。 将图 3-2 中大部分零乱内容移除可以得到图3-3所示的UML类图。子接口API代码如下:
public interface ReadableByteChannel extends Channel
{
public int read (ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel extends Channel
{
public int write (ByteBuffer src) throws IOException;
}
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
{
}
通道可以是单向(unidirectional)或者双向的(bidirectional)。一个channel类可能实现定义read()
方法的ReadableByteChannel
接口,而另一个channel类也许实现WritableByteChannel
接口以提供write()
方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。
图 3-3 显示了一个ByteChannel
接口,该接口引申出了ReadableByteChannel
和WritableByteChannel
两个接口。ByteChannel
接口本身并不定义新的API方法,它是一种用来聚集它自己以一个新名称继承的多个接口的便捷接口。根据定义,实现ByteChannel
接口的通道会同时实现ReadableByteChannel
和WritableByteChannel
两个接口,所以此类通道是双向的。这是简化类定义的语法糖(syntactic sugar),它使得用操作器(operator)实例来测试通道对象变得更加简单。
这是一种好的类设计技巧,如果您在写您自己的Channel实现的话,您可以适当地实现这些接口。不过对于使用java.nio.channels
包中标准通道类的程序员来说,这些接口并没有太大的吸引力。假如您快速回顾一下图 3-2 或者向前跳跃到关于file和socket通道的章节,您将发现每一个file或socket通道都实现全部三个接口。从类定义的角度而言,这意味着全部file和socket通道对象都是双向的。这对于sockets不是问题,因为它们一直都是双向的,不过对于files却是个问题了。
我们知道,一个文件可以在不同的时候以不同的权限打开。从FileInputStream
对象的getChannel()
方法获取的FileChannel
对象是只读的,不过从接口声明的角度来看却是双向的,因为FileChannel
实现ByteChannel
接口。在这样一个通道上调用write()
方法将抛出未经检查的NonWritableChannelException
异常,因为FileInputStream
对象总是以read-only的权限打开文件。
通道会连接一个特定I/O服务且通道实例(channel instance)的性能受它所连接的 I/O 服务的特征限制,记住这很重要。一个连接到只读文件的Channel实例不能进行写操作,即使该实例所属的类可能有write()
方法。基于此,程序员需要知道通道是如何打开的,避免试图尝试一个底层 I/O服务不允许的操作。
// A ByteBuffer named buffer contains data to be written
FileInputStream input = new FileInputStream (fileName);
FileChannel channel = input.getChannel( );
// This will compile but will throw an IOException
// because the underlying file is read-only
channel.write (buffer);
根据底层文件句柄的访问模式,通道实例可能不允许使用read()
或write()
方法。
ByteChannel
的read()
和write()
方法使用ByteBuffer
对象作为参数。两种方法均返回已传输的字节数,可能比缓冲区的字节数少甚至可能为零。缓冲区的位置也会发生与已传输字节相同数量的前移。如果只进行了部分传输,缓冲区可以被重新提交给通道并从上次中断的地方继续传输。该过程重复进行直到缓冲区的hasRemaining()
方法返回false
值。例 3-1 表示了如何从一个通道复制数据到另一个通道。
package com.ronsoft.books.nio.channels;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.Channels;
import java.io.IOException;
/**
* Test copying between channels.
* @author Ron Hitchens(ron@ronsoft.com)
*/
public class ChannelCopy{
/**
* This code copies data from stdin to stdout. Like the 'cat'
* command, but without any useful options.
*/
public static void main (String [] argv) throws IOException{
ReadableByteChannel source = Channels.newChannel(System.in);
WritableByteChannel dest = Channels.newChannel(System.out);
channelCopy1(source, dest);
// alternatively, call channelCopy2 (source, dest);
source.close();
dest.close();
}
/**
* Channel copy method 1. This method copies data from the src
* channel and writes it to the dest channel until EOF on src.
* This implementation makes use of compact( ) on the temp buffer
* to pack down the data if the buffer wasn't fully drained. This
* may result in data copying, but minimizes system calls. It also
* requires a cleanup loop to make sure all the data gets sent.
*/
private static void channelCopy1 (ReadableByteChannel src, WritableByteChannel dest) throws IOException{
ByteBuffer buffer = ByteBuffer.allocateDirect (16 * 1024);
while (src.read(buffer) != -1) {
// Prepare the buffer to be drained
buffer.flip();
// Write to the channel; may block
dest.write(buffer);
// If partial transfer, shift remainder down
// If buffer is empty, same as doing clear()
buffer.compact();
}
// EOF will leave buffer in fill state
buffer.flip();
// Make sure that the buffer is fully drained
while (buffer.hasRemaining()) {
dest.write(buffer);
}
}
/**
* Channel copy method 2. This method performs the same copy, but
* assures the temp buffer is empty before reading more data. This
* never requires data copying but may result in more systems calls.
* No post-loop cleanup is needed because the buffer will be empty
* when the loop is exited.
*/
private static void channelCopy2 (ReadableByteChannel src, WritableByteChannel dest) throws IOException{
ByteBuffer buffer = ByteBuffer.allocateDirect (16 * 1024);
while (src.read (buffer) != -1) {
// Prepare the buffer to be drained
buffer.flip();
// Make sure that the buffer was fully drained
while (buffer.hasRemaining()) {
dest.write(buffer);
}
// Make the buffer empty, ready for filling
buffer.clear();
}
}
}
通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如 sockets 和 pipes 才能使用非阻塞模式。
从图 3-2 可以看出,socket通道类从SelectableChannel
引申而来。从SelectableChannel
引申而来的类可以和支持有条件的选择(readiness selectio)的选择器(Selectors)一起使用。将非阻塞I/O 和选择器组合起来可以使您的程序利用多路复用 I/O(multiplexed I/O)。选择和多路复用将在 第四章中予以讨论。关于怎样将sockets置于非阻塞模式的细节会在3.5节中涉及。
0 条评论
撰写评论