JavaNIO-通道
程序员文章站
2022-06-02 23:52:23
...
Channel
这里的Channel接口很细。一个Channel能做什么呢?
很简单吧。这个也太抽象了。通道就是这样。其实和我们看到的管道一样。可以关闭,可以问,这个管道通不通?
当然,最主要的还是我们如何使用呢?读和写永远是两个孪生操作。
对一个管道的字节读,有 ReadableByteChannel, WritableByteChannel,和
InterruptibleChannel。 有了这三个接口,我们的管道就成型了。
然后,接下来的接口便是 ByteChannel 之类的。ByteChannel类继承了ReadableByteChannel和WritableByteChannel的属性。然后还有很多很多的Channel了。例如:SocketChannel,FileChannel,ServerSocketChannel等等。
那么,如何打开一个管道呢。
打开一个管道,通常就是创建一个管道。这里,如果是SocketChannel,我们可以直接使用open方法,如果是FileChannel,则需要在一个已经打开的文件上使用。例如:RandomAccessFile, FileInputStream, FileOutputStream上调用 getChannel()方法。
管道打开,接下来就是读写管道。根据ReadableByteChannel和WritableByteChannel接口来看。
这下,可以和我们之前的缓冲区结合起来了。注意,管道只和ByteBuffer打交道。
这里还要注意,SocketChannel和FileChannel都是实现了以上三个接口的。那么,也就是说,他们都有读写的方法,但是对于FileChannel来说,并非如此,因为有的文件可能是只读的,那么,如果调用write方法的,就会抛出NonWritableChannelException异常。
下面看一个简单的例子。
这个例子中,我们又看到一种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就登场了。
看到这两个接口,我们就一下明白了吧。
还是copy一个片段例子来看看。假设这个channel是一个有48个字节的socketChannel
那么,首先把header填充满10个字节,然后剩余38个字节就是body的了。
然后我们可能这样使用:
这里直接使用body来处理,是不是很方便呢。
好了。下面将是FileChannel和SocketChannel的介绍了。这次就到这里。
这里的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的介绍了。这次就到这里。