NIO总结(二) NIO之Channel

Channel

NIO把它支持的I/O对象抽象为Channel,Channel又称“通道”,类似于原I/O中的流(Stream),但有所区别:

1、流是单向的,通道是双向的,可读可写。

2、流读写是阻塞的,通道可以异步读写。

3、流中的数据可以选择性的先读到缓存中,通道的数据总是要先读到一个缓存中,或从缓存中写入
在这里插入图片描述

在这里插入图片描述

eg:使用FileChannel(一个用来写、读、映射和操作文件的通道)进行文件复制

第一种

在这里插入图片描述

FileChannel的read、write和map通过其实现类FileChannelImpl实现,自身为抽象方法

Read()方法底层实现原理

FileChannelImpl中read代码实现

public int read(ByteBuffer dst) throws IOException {
    ensureOpen();
    if (!readable)
        throw new NonReadableChannelException();
    synchronized (positionLock) {
        int n = 0;
        int ti = -1;
        try {
            begin();
            ti = threads.add();
            if (!isOpen())
                return 0;
            do {
                n = IOUtil.read(fd, dst, -1, nd);
            } while ((n == IOStatus.INTERRUPTED) && isOpen());
            return IOStatus.normalize(n);
        } finally {
            threads.remove(ti);
            end(n > 0);
            assert IOStatus.check(n);
        }
    }
}

由代码可知FileChannelImpl的read方法通过IOUtil的read实现,那我们接下来看。

IOUtil中read代码实现

static int read(FileDescriptor fd, ByteBuffer dst, long position,
                NativeDispatcher nd) IOException {
    if (dst.isReadOnly())
        throw new IllegalArgumentException("Read-only buffer");
    if (dst instanceof DirectBuffer)
        return readIntoNativeBuffer(fd, dst, position, nd);

    // Substitute a native buffer
    ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
    try {
        int n = readIntoNativeBuffer(fd, bb, position, nd);
        bb.flip();
        if (n > 0)
            dst.put(bb);
        return n;
    } finally {
        Util.offerFirstTemporaryDirectBuffer(bb);
    }
}

通过上述实现可以看出,基于channel的文件数据读取步骤如下:

1、申请一块和缓存同大小的DirectByteBuffer bb。

2、读取数据到缓存bb,底层由NativeDispatcher的read实现。

3、把bb的数据读取到dst(用户定义的缓存,在jvm中分配内存)。

read方法导致数据复制了两次。

Write()方法底层实现原理

FileChannelImpl中read代码实现

public int write(ByteBuffer src) throws IOException {
    ensureOpen();
    if (!writable)
        throw new NonWritableChannelException();
    synchronized (positionLock) {
        int n = 0;
        int ti = -1;
        try {
            begin();
            ti = threads.add();
            if (!isOpen())
                return 0;
            do {
                n = IOUtil.write(fd, src, -1, nd);
            } while ((n == IOStatus.INTERRUPTED) && isOpen());
            return IOStatus.normalize(n);
        } finally {
            threads.remove(ti);
            end(n > 0);
            assert IOStatus.check(n);
        }
    }
}

由代码可知FileChannelImpl的write方法也是通过IOUtil的write实现,那我们接下来看。

IOUtil中write代码实现

static int write(FileDescriptor fd, ByteBuffer src, long position,
                 NativeDispatcher nd) throws IOException {
    if (src instanceof DirectBuffer)
        return writeFromNativeBuffer(fd, src, position, nd);
    // Substitute a native buffer
    int pos = src.position();
    int lim = src.limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
    try {
        bb.put(src);
        bb.flip();
        // Do not update src until we see how many bytes were written
        src.position(pos);
        int n = writeFromNativeBuffer(fd, bb, position, nd);
        if (n > 0) {
            // now update src
            src.position(pos + n);
        }
        return n;
    } finally {
        Util.offerFirstTemporaryDirectBuffer(bb);
    }
}

通过上述实现可以看出,基于channel的文件数据写入步骤如下:

1、申请一块DirectByteBuffer,bb大小为byteBuffer中的limit - position。

2、复制byteBuffer中的数据到bb中。

3、把数据从bb中写入到文件,底层由NativeDispatcher的write实现

write方法也导致了数据复制了两次

第二种

在这里插入图片描述

第三种

在这里插入图片描述

eg:使用ServerSocketChannel实现简单的网络传输

在这里插入图片描述

在这里插入图片描述

分散(Scatter)与聚集(Gather)

分散读取:将通道中的数据分散到多个缓冲区中
聚集写入:将多个缓冲区中的数据聚集到通道中

注:按照缓冲区的顺序,从Channel中读取的数据依此将Buffer填满,或从Buffer读取到Channel中
在这里插入图片描述

发布了45 篇原创文章 · 获赞 3 · 访问量 2321

猜你喜欢

转载自blog.csdn.net/weixin_44046437/article/details/99648626