Java NIO之Channel

channel

Channel是仅次于Buffer的重要模块, Channel是IO设备传输的媒介.
通常来说, 操作系统的每个文件标识符都会有相应的channel对应. Channel类不仅提供了平台无关性的抽象, 并且同样支持现代操作系统的本地io能力.
Channel提供了使用操作系统的本地io的服务, 并且只消耗很小. Buffer就是传输过程数据的容器.

Java NIO Channel

Channel接口提供了两个方法如下

public interface Channel extends Closeable {

   public boolean isOpen();

   public void close() throws IOException;
}

isOpen()用于判断channel是否打开;
close()用于关闭一个打开的channel.

如何打开一个channel

io操作一共就两种, 文件io和网络io. 所以也就有两大类channel: FileChannelSocketChannel.
下面通过几个例子来看下如何创建Channel.

创建FileChannel

FileChannel只能通过文件流使用getChannel()方法获取. 文件流指的是RandomAccessFile, FileInputStream, FileOutputStream. FileChannel是不能直接创建的.

RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel();

创建SocketChannel

FileChannel不同, SocketChannel有可以创建的工厂方法.

//SocketChannel
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("somehost", someport));
 
//ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind (new InetSocketAddress (somelocalport));
 
//DatagramChannel
DatagramChannel dc = DatagramChannel.open();

以上方法都会创建一个channel对象, 与FileChannel不同的是, SocketChannel如果已经存在的话是不会创建一个新的.

如何使用Channel

可以看到Channel接口只提供了两个方法, 没有提供关于读取的方法, 那么channel读取的功能就需要子类来实现. Channel接口有两个重要的子类负责读取操作:

public interface ReadableByteChannel extends Channel{
    public int read (ByteBuffer dst) throws IOException;
}
 
public interface WritableByteChannel extends Channel{
    public int write (ByteBuffer src) throws IOException;
}

从名字就能知道每个子类的功能. 如果同时想操作读取则可以直接实现ByteChannel

public interface ByteChannel extends ReadableByteChannel, WritableByteChannel{
}

对, 没看错, 里面啥也没有, 该接口设计的目的应该就是为了不用每次都实现两个接口.
继承关系如图


image

上面说的FileChannelSocketChannel都继承了ByteChannel, 所以都可以执行读写. 聪明如你看出问题了么?
对, 对于文件流来说, 通过FileInputStream获取到的FileChannel是"只读"的, 所以此时执行写操作会抛出异常.

ByteChannelread()write()方法的参数是ByteBuffer. 两个方法的返回参数是int, 表示已经传输的字节数量. 返回值可以小于buffer中的字节数, 甚至可以是0. buffer中的position也会向前移动的一样的距离.

如果一个buffer只传输了一部分数据, 那么我们还可以继续提交buffer给channel继续传输剩下的部分. 重复上述步骤, 直到hasRemaining()方法返回false.

下面的例子是从一个channel复制数据到另一个channel

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class ChannelCopyExample {
    
    public static void main(String[] args) throws IOException {

        FileInputStream fileInputStream = new FileInputStream("in.txt");
        ReadableByteChannel readableByteChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
        WritableByteChannel writableByteChannel = fileOutputStream.getChannel();

        copyData(readableByteChannel, writableByteChannel);

        readableByteChannel.close();
        writableByteChannel.close();
    }

    private static void copyData(ReadableByteChannel readableByteChannel, WritableByteChannel writableByteChannel) throws IOException {

        ByteBuffer buffer = ByteBuffer.allocate(1 * 1024);
        while (readableByteChannel.read(buffer) != -1) {

            buffer.flip();

            while (buffer.hasRemaining()) {
                writableByteChannel.write(buffer);
            }

            buffer.clear();
        }
    }
}

channel有两种工作模式: 阻塞模式和非阻塞模式. 当channel在非阻塞模式下时, channel不会将工作线程设置为睡眠状态. 而且请求操作的响应会立即返回, 可能是处理完了, 也可能是没有处理完. 也就是说在非阻塞模式下, 请求会马上得到结果不会阻塞主工作线程. 只有流式的channel才可以设置为非阻塞状态, 比如socket和pipe.

channel的关闭

关闭channel只需要调用close()方法, 与buffer不同的是当channel关掉后就不能再重复使用了. 一个打开的channel代表了和某个io服务的连接, 而且还封装连接的状态信息. 当channel关闭时, 连接也就丢失了, 此时channel不再连接到任何io设备.

我们可以通过isOpen()方法判断当前channel是否关闭, true表示打开, false表示关闭. 当对一个关闭channel读写或者其他需要channel打开的操作都会抛出异常.

参考链接 https://howtodoinjava.com/java/nio/java-nio-2-0-channels/

你可能感兴趣的:(Java NIO之Channel)