Java文件NIO读取的本质——FileInputStream与FileChannel对比

本文连接:https://www.jianshu.com/p/a23ca155f949
本文作者:[email protected]

仓促成文,还请指正。

FileInputStream典型代码


    public static void main(String[] args) {
        System.out.println(System.getProperty("user.dir"));
        File file = new File(System.getProperty("user.dir") + "/src/oio/file.txt");
        System.out.println("file name: " + file.getName());

        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            byte[] bytes = new byte[(int) file.length()];
            int len = inputStream.read(bytes);
            System.out.println("bytes len :" + len + " detail: " + new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

FileChannel典型代码


public class NIOTest {

    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(4);//①
        Path path = Paths.get(System.getProperty("user.dir") + "/assets/file.txt");
        FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);//②
        int len = fileChannel.read(byteBuffer);//③
        while (len != -1) {
            byteBuffer.flip();//④
            while (byteBuffer.hasRemaining()){
                System.out.print((char) byteBuffer.get());//⑤
            }
            byteBuffer.clear();//⑥
            len = fileChannel.read(byteBuffer);//⑦
        }
    }
}

FileInputStream和FileChannel的深度分析

FileInputStream的read方法,调用了native的read0

  1. http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/native/java/io/io_util.c的jint[] readSingle(JNIEnv *env, jobject this, jfieldID fid)
jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
    jint nread;
    char ret;
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return -1;
    }
    nread = IO_Read(fd, &ret, 1);
    if (nread == 0) { /* EOF */
        return -1;
    } else if (nread == -1) { /* error */
        JNU_ThrowIOExceptionWithLastError(env, "Read error");
    }
    return ret & 0xFF;
}

核心是IO_Read方法。

  1. http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/windows/native/java/io/io_util_md.h定义了宏
#define IO_Read handleRead
  1. http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/windows/native/java/io/io_util_md.c的handleRead方法:
JNIEXPORT
jint
handleRead(FD fd, void *buf, jint len)
{
    DWORD read = 0;
    BOOL result = 0;
    HANDLE h = (HANDLE)fd;
    if (h == INVALID_HANDLE_VALUE) {
        return -1;
    }
    result = ReadFile(h,          /* File handle to read */
                      buf,        /* address to put data */
                      len,        /* number of bytes to read */
                      &read,      /* number of bytes read */
                      NULL);      /* no overlapped struct */
    if (result == 0) {
        int error = GetLastError();
        if (error == ERROR_BROKEN_PIPE) {
            return 0; /* EOF */
        }
        return -1;
    }
    return (jint)read;
}

核心方法是ReadFile方法。

FileChannel的read方法

  1. 使用FIleChannelImpl作为FileChannel的实现类,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);
           }
       }
   }

核心方法是IOUtil.read。

  1. 进入IOUtil类:
    static int read(FileDescriptor fd, ByteBuffer dst, long position,
                    NativeDispatcher nd)
        throws 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);
        }
    }

核心方法是readIntoNativeBuffer。

  1. 进入readIntoNativeBuffer方法
    private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
                                            long position, NativeDispatcher nd)
        throws IOException
    {
        int pos = bb.position();
        int lim = bb.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);

        if (rem == 0)
            return 0;
        int n = 0;
        if (position != -1) {
            n = nd.pread(fd, ((DirectBuffer)bb).address() + pos,
                         rem, position);
        } else {
            n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem);
        }
        if (n > 0)
            bb.position(pos + n);
        return n;
    }

核心方法是nd.pread(或nd.read,本质上一样)。这里的nd是抽象类sun.nio.ch.NativeDispatcher,具体类是sun.nio.ch.FileDispatcherImpl

  1. 进入sun.nio.ch.FileDispatcherImpl类:
    int read(FileDescriptor var1, long var2, int var4) throws IOException {
        return read0(var1, var2, var4);
    }

最终进入了一个native的read0方法。

  1. 根据openjdk1.7,查看这个native方法,在http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/windows/native/sun/nio/ch/FileDispatcherImpl.c中(以windows为例):
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_read0(JNIEnv *env, jclass clazz, jobject fdo,
                                      jlong address, jint len)
{
    DWORD read = 0;
    BOOL result = 0;
    HANDLE h = (HANDLE)(handleval(env, fdo));

    if (h == INVALID_HANDLE_VALUE) {
        JNU_ThrowIOExceptionWithLastError(env, "Invalid handle");
        return IOS_THROWN;
    }
    result = ReadFile(h,          /* File handle to read */
                      (LPVOID)address,    /* address to put data */
                      len,        /* number of bytes to read */
                      &read,      /* number of bytes read */
                      NULL);      /* no overlapped struct */
    if (result == 0) {
        int error = GetLastError();
        if (error == ERROR_BROKEN_PIPE) {
            return IOS_EOF;
        }
        if (error == ERROR_NO_DATA) {
            return IOS_UNAVAILABLE;
        }
        JNU_ThrowIOExceptionWithLastError(env, "Read failed");
        return IOS_THROWN;
    }
    return convertReturnVal(env, (jint)read, JNI_TRUE);
}

核心方法是ReadFile方法。

结论

FileInputStream和FileChannel最终均调用了native的ReadFile方法,本质是一样的!

你可能感兴趣的:(Java文件NIO读取的本质——FileInputStream与FileChannel对比)