1 通道基础
package java.nio.channels;
public interface Channel
{
public boolean isOpen( );
public void close( ) throws IOException;
}
对于通道来说,只有两种共同的操作:检查通道是否打开(isOpen())和关闭一个打开的通道(close())。
InterruptibleChannel是一个标记接口,当被通道使用时可以标示该通道是可以中断的,继承了Channel接口的其他接口都是面向字节的子接口,包括WritableByteChannel (可写字节通道)和 ReadableByteChannel(可读字节通道)。
1.1 打开通道
通道是访问I/O服务的导管。
I/O可以分为广义的两大类别:File I/O和Stream I/O。相应地有两种类型的通道:文件(file)通道和套接字(socket)通道。
Socket通道有可以直接创建新socket通道的工厂方法,FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或 FileOutputStream对象上调用getChannel( )方法来获取。
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress ("somehost", someport));
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (somelocalport));
DatagramChannel dc = DatagramChannel.open( );
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel( );
1.2 使用通道
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
{
}
一个channel类可能实现了ReadableByteChannel 接口,它定义了read( ) 方法,而另一个channel类可能实现了WritableByteChannel接口,它提供了write( ) 方法。。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。
一个文件可以在不同的时候以不同的权限打开。从FileInputStream对象的getChannel( )方法获取的FileChannel 对象是只读的。不过从接口声明的角度来看却是双向的,因为FileChannel 实现ByteChannel接口。在这样一个通道上调用write( ) 方法将抛出未经检查的NonWritableChannelException异常,因为FileInputStream对象总是以read -only 的权限打开文件。
通道会连接一个特定I/O服务且通道实例(channel instance )的性能受它所连接的I/O服务的特征限制。
ByteChannel的read( ) 和write( ) 方法使用ByteBuffer对象作为参数,两种方法均返回已传输的字节数,可能比缓冲区的字节数少甚至可能为零。缓冲区的位置也会发生与已传输字节相同数量的前移。如果只进行了部分传输,缓冲区可以被重新提交给通道并从上次中断的地方继续传输。该过程重复进行直到缓冲区的hasRemaining( ) 方法返回false 值
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);
}
}
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( );
}
}
通道可以以阻塞或者非阻塞模式运行,非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的通道,如socket和pipes才能使用非阻塞模式。
继承SelectableChannel的类可以和Selector一起使用,后者支持就绪选择(readiness selection )。将非阻塞I/O和选择器组合起来可以使您的程序利用多路复用I/O(multiplexed I/O)
1.4 关闭通道
通道不能被重复使用。一个打开的通道即代表与一个特定IO服务的特定连接并封装该连接的状态。当通道关闭时,连接会丢失,然后通道将不再连接任何东西。
调用通道偶的close()方法,可能会导致在通道关闭底层IO服务的过程中线程暂时中断,哪怕通道处于非阻塞模式。
2 Scatter/Gather
Scatter/Gather是一个简单却强大的概念,它是指在多个缓冲区上实现一个简单的IO操作。对于write操作,数据是从几个缓冲区按顺序抽取(gather)并沿着通道发送。该Gather过程的效果就好比全部缓冲区的内容被连接起来,并在发送数据前存放到一个大的缓冲区中。对于read操作而言,从通道读取数据会按顺序被散布到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间呗消耗完。
public interface ScatteringByteChannel
extends ReadableByteChannel
{
public long read (ByteBuffer [] dsts) throws IOException;
public long read (ByteBuffer [] dsts, int offset, int length) throws IOException;
}
public interface GatheringByteChannel
extends WritableByteChannel
{
public long write(ByteBuffer[] srcs) throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}
ByteBuffer header = ByteBuffer.allocateDirect (10);
ByteBuffer body = ByteBuffer.allocateDirect (80);
ByteBuffer [] buffers = { header, body };
int bytesRead = channel.read (buffers);
假定channel连接到一个有 48字节数据等待读取的socket 上,一旦read( ) 方法返回,bytesRead就被赋予值 48,header缓冲区将包含前 10 个从通道读取的字节而body缓冲区则包含接下来的 38个字节。通道会自动地将数据scatter 到这两个缓冲区中。
我们可以用一个gather操作将多个缓冲区的数据组合并发送出去。
body.clear( );
body.put("FOO".getBytes()).flip( ); // "FOO" as bytes
header.clear( );
header.putShort (TYPE_FILE).putLong (body.limit()).flip( );
long bytesWritten = channel.write (buffers);
带offset和length参数版本的read( ) 和write( ) 方法使得我们可以使用缓冲区数组的子集缓冲区。这里的offset值指哪个缓冲区将开始被使用,而不是指数据的offset。这里的length 参数指示要使用的缓冲区数量