Java文件NIO讀取的本質——FileInputStream與FileChannel對比

本文連接:http://www.lxweimin.com/p/a23ca155f949
本文作者:gks09@qq.com

倉促成文,還請指正。

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.cjint[] 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方法,本質是一樣的!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。