首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

JavaNIO-通路

2012-08-22 
JavaNIO-通道Channel这里的Channel接口很细。一个Channel能做什么呢?package java.nio.channels public in

JavaNIO-通道
Channel
这里的Channel接口很细。一个Channel能做什么呢?

package java.nio.channels; public interface Channel { public boolean isOpen( ); public void close( ) throws IOException; }


很简单吧。这个也太抽象了。通道就是这样。其实和我们看到的管道一样。可以关闭,可以问,这个管道通不通?

当然,最主要的还是我们如何使用呢?读和写永远是两个孪生操作。
对一个管道的字节读,有 ReadableByteChannel, WritableByteChannel,和
InterruptibleChannel。 有了这三个接口,我们的管道就成型了。
然后,接下来的接口便是 ByteChannel 之类的。ByteChannel类继承了ReadableByteChannel和WritableByteChannel的属性。然后还有很多很多的Channel了。例如:SocketChannel,FileChannel,ServerSocketChannel等等。

那么,如何打开一个管道呢。
打开一个管道,通常就是创建一个管道。这里,如果是SocketChannel,我们可以直接使用open方法,如果是FileChannel,则需要在一个已经打开的文件上使用。例如:RandomAccessFile, FileInputStream, FileOutputStream上调用 getChannel()方法。
管道打开,接下来就是读写管道。根据ReadableByteChannel和WritableByteChannel接口来看。
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 {}

这下,可以和我们之前的缓冲区结合起来了。注意,管道只和ByteBuffer打交道。

这里还要注意,SocketChannel和FileChannel都是实现了以上三个接口的。那么,也就是说,他们都有读写的方法,但是对于FileChannel来说,并非如此,因为有的文件可能是只读的,那么,如果调用write方法的,就会抛出NonWritableChannelException异常。

下面看一个简单的例子。
package shaoxin.nio;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.Channel;import java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;import java.nio.channels.WritableByteChannel;public class ChannelCopy {public static void main(String []args) throws Exception{ChannelCopy copyc = new ChannelCopy();ReadableByteChannel readChannel = Channels.newChannel(System.in);WritableByteChannel writeChannel = Channels.newChannel(System.out);copyc.copyChannel(readChannel, writeChannel);readChannel.close();writeChannel.close();}public void copyChannel(ReadableByteChannel from ,WritableByteChannel to)throws Exception{ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.flip();while(from.read(buffer)!=-1){buffer.flip();to.write(buffer);buffer.compact();}buffer.flip();while(buffer.hasRemaining()){to.write(buffer);}}public void copyChannel2(ReadableByteChannel from ,WritableByteChannel to)throws IOException{ByteBuffer buffer  = ByteBuffer.allocate(1024);while(from.read(buffer)!=-1){buffer.flip();while(buffer.hasRemaining()){to.write(buffer);}buffer.clear();}}}

这个例子中,我们又看到一种Channel的创建方法,使用Channels的newChannel方法来包装一个流。这里的copyChannel有两种方法。第一种,使用compact来压缩,可以减少系统调用,对于后一种,则是每次都吧缓冲区中的数据写入管道。这种方法可能会导致更多的IO调用。大家可以自己思考为什么。

这里还有些特性没讲。例如阻塞和非组塞,还有非阻塞和Selector一起使用,可以实现多路复用(multipluxed I/O)。这些都在后面有介绍。

关闭一个通道调用close。注意这可能引起系统阻塞。因为通道关闭底层的I/O的线程可能阻塞。多次调用close没有关系。第一个阻塞后面的close,后面的close如果发现通道已经关闭就直接返回。
这里复杂的是引入InterruptibleChannel。如果一个实现了InterruptibleChannel的接口的通道,在一个线程上阻塞了,并切这个线程被中断(调用interrupt()),那么这个通道被关闭,并且这个线程产生一个ClosedByInterruptException异常。
中断往往是通过一个中断标志来标记的。如果一个被标记为中断的线程,试图访问一个通道,那么同样抛出ClosedByInterruptException异常。
使用 Thread.interrupted()可以清除中断标志。

这个设计看起来很严格,实际上也是为了跨平台的实现设计的。因为不可能在不同平台上要求对中断后的通道产生一直可靠的I/O操作。

这样的设计,也说明了,可中断的通道是可以异步关闭的。如果有其他的线程在等待改通道,那么通道关闭时,将会给所有等待的线程唤醒并抛出一个AsynchronousCloseException异常。
一般来说,不实现InterruptibleChannel的通道都是不进行底层代码实现的特殊通道,他们永不阻塞。

接下来,可能要啰嗦一段。那就是为了提高I/O性能,我们有时候可能要组合多个缓冲区,或者从多个缓冲区读数据。这时,Scatter和Gather就登场了。

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; }

看到这两个接口,我们就一下明白了吧。
还是copy一个片段例子来看看。假设这个channel是一个有48个字节的socketChannel
ByteBuffer header = ByteBuffer.allocateDirect (10); ByteBuffer body = ByteBuffer.allocateDirect (80); ByteBuffer [] buffers = { header, body }; int bytesRead = channel.read (buffers);

那么,首先把header填充满10个字节,然后剩余38个字节就是body的了。
然后我们可能这样使用:
switch (header.getShort(0)) { case TYPE_PING:   break; case TYPE_FILE:   body.flip( );    fileChannel.write (body);   break; default:    logUnknownPacket (header.getShort(0), header.getLong(2), body);   break; }

这里直接使用body来处理,是不是很方便呢。

好了。下面将是FileChannel和SocketChannel的介绍了。这次就到这里。

热点排行