【Java8源码分析】NIO包-FileChannel

来源:互联网 发布:relief算法应用 编辑:程序博客网 时间:2024/05/28 15:03

转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72910485


1 概述

Java NIO 由以下几个核心部分组成:

  • Buffer
  • Channel
  • Selectors



相关类的使用方法可以参考Java NIO 系列教程,写的通俗易懂。

本文主要从源码方面分析一下Channel类。


2 Channel介绍

Java NIO的通道类似流,但又有些不同:

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
  • 通道可以异步地读写。
  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

Java NIO中最重要的通道的实现有以下几种

  • FileChannel:从文件中读写数据
  • DatagramChannel:能通过UDP读写网络中的数据
  • SocketChannel:能通过TCP读写网络中的数据
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

在Java中,Channel是一个简单的接口,只有两个函数,如下所示

public interface Channel extends Closeable {    public boolean isOpen();    public void close() throws IOException;}

3 FileChannel

FileChannel主要是用来读、写和映射一个系统文件的Channel,它是一个抽象类,具体由FileChannelImpl实现。

public abstract class FileChannel {    // 构造函数    protected FileChannel() {    }    // 写入buffer    public abstract int read(ByteBuffer dst) throws IOException;    public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;    public final long read(ByteBuffer[] dsts) throws IOException {        return read(dsts, 0, dsts.length);    }    // 从buffer读取    public abstract int write(ByteBuffer src) throws IOException;    public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;    public final long write(ByteBuffer[] srcs) throws IOException {        return write(srcs, 0, srcs.length);    }}

3 FileChannelImpl

3.1 打开通道

有两种方法可以打开一个FileChannel,类似于打开一个字节流文件

(1)通过InputStream、OutputStream或RandomAccessFile来获取一个FileChannel(常用),调用其中一个得getChannel函数即可

public FileChannel getChannel() {        synchronized (this) {            if (channel == null) {                channel = FileChannelImpl.open(fd, true, false, this);            }            return channel;        }    }

(2)通过FileChannel的静态方法,直接打开

public static FileChannel open(Path path,        Set<? extends OpenOption> options,        FileAttribute<?>... attrs) throws IOException{    FileSystemProvider provider = path.getFileSystem().provider();    return provider.newFileChannel(path, options, attrs);}

3.2 read

public int read(ByteBuffer dst) throws IOException {    ensureOpen();    if (!readable)        throw new NonReadableChannelException();    // 1 写入缓冲区需要加锁    synchronized (positionLock) {        int n = 0;        int ti = -1;        try {            begin();            ti = threads.add();            if (!isOpen())                return 0;            do {                // 2 通过IOUtil.read实现                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);        }    }}

主要步骤如下:

  1. 写入缓冲区的操作需要加锁,保证多线程安全
  2. 临界区中通过IOUtil实现向buffer的写入,IOUtil的read函数如下
static int read(FileDescriptor fd, ByteBuffer dst, long position, NativeDispatcher nd) throws IOException{    // 1 申请一块临时堆外DirectByteBuffer    ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());    try {        // 2 先往DirectByteBuffer写入数据,提高效率        int n = readIntoNativeBuffer(fd, bb, position, nd);        bb.flip();        if (n > 0)            // 3 再拷贝到传入的buffer            dst.put(bb);        return n;    } finally {        Util.offerFirstTemporaryDirectBuffer(bb);    }}

主要步骤如下:

  1. 申请一块临时堆外DirectByteBuffer,大小同传入的buffer,不了解DirectByteBuffer的可以参考内存映射文件DirectByteBuffer与MappedByteBuffer一文
  2. 先往DirectByteBuffer写入数据,这样能提高效率
  3. 再把DirectByteBuffer数据拷贝到用户传入的buffer

3.3 write

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 {                // 2 通过IOUtil.write实现                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);        }    }}

主要步骤如下:

  1. 读取缓冲区的操作需要加锁,保证多线程安全
  2. 临界区中通过IOUtil实现从buffer的读取,IOUtil的write函数如下
static int write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd)   throws IOException{   // 1 如果传入的缓冲区是DirectBuffer,直接从里面读取   if (src instanceof DirectBuffer)       return writeFromNativeBuffer(fd, src, position, nd);   int pos = src.position();   int lim = src.limit();   assert (pos <= lim);   int rem = (pos <= lim ? lim - pos : 0);   // 2 否则构造一块跟传入缓冲区一样大小的DirectBuffer   ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);   try {       bb.put(src);       bb.flip();       src.position(pos);       // 3 调用writeFromNativeBuffer读取       int n = writeFromNativeBuffer(fd, bb, position, nd);       if (n > 0) {           // now update src           src.position(pos + n);       }       return n;   } finally {       Util.offerFirstTemporaryDirectBuffer(bb);   }}

主要步骤如下:

  1. 判断传入的缓冲区是否是DirectByteBuffer,是的话直接读取
  2. 否则构造一块跟传入缓冲区一样大小的DirectBuffer
  3. 调用writeFromNativeBuffer读取

4 总结

(1)虽然FileChannel在NIO包中,但是FileChannel的读取只能是阻塞的,而像DatagramChannel、SocketChannel则可设置为阻塞的。


5 参考

http://ifeve.com/file-channel/


转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72910485

原创粉丝点击