Java 文件NIO 中,为什么NIO比IO快?

首先我们来看下传统的IO读取

private native int readBytes(byte b[], int off, int len)

这个native方法源码在openjdk中文件名就是FileInputStream.c,方法如下:

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
        jbyteArray bytes, jint off, jint len) {
 return readBytes(env, this, bytes, off, len, fis_fd);
}

然后又找到readBytes方法:

jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
 jint off, jint len, jfieldID fid)
{
 jint nread;
 char stackBuf[BUF_SIZE];
 char *buf = NULL;
 FD fd;

 if (IS_NULL(bytes)) {
 JNU_ThrowNullPointerException(env, NULL);
 return -1;
    }

 if (outOfBounds(env, off, len, bytes)) {
 JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
 return -1;
    }

 if (len == 0) {
 return 0;
    } else if (len > BUF_SIZE) {
 buf = malloc(len);
 if (buf == NULL) {
 JNU_ThrowOutOfMemoryError(env, NULL);
 return 0;
        }
    } else {
 buf = stackBuf;
    }

    fd = GET_FD(this, fid);
 if (fd == -1) {
 JNU_ThrowIOException(env, "Stream Closed");
        nread = -1;
    } else {
        nread = IO_Read(fd, buf, len);
 if (nread > 0) {
            (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
        } else if (nread == JVM_IO_ERR) {
 JNU_ThrowIOExceptionWithLastError(env, "Read error");
        } else if (nread == JVM_IO_INTR) {
 JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
        } else { /* EOF */
            nread = -1;
        }
    }

 if (buf != stackBuf) {
 free(buf);
    }
 return nread;
}

接着又定位到IO_Read(fd, buf, len);这行

#define IO_Read JVM_Read

IO_Read又是宏定义,最终调用的是JVM_Read方法

/*
 * Read data from a file decriptor into a char array.
 *
 * fd        the file descriptor to read from.
 * buf       the buffer where to put the read data.
 * nbytes    the number of bytes to read.
 *
 * This function returns -1 on error, and 0 on success.
 */
JNIEXPORT jint JNICALL
JVM_Read(jint fd, char *buf, jint nbytes);

解释很清楚了,将数据从文件解析器读入字符数组;


接着我们看看NIO中的读取,通常我们可以这样简单读取

/* 获得源文件的输入字节流 */
FileInputStream fin = new FileInputStream(new File("D:\\2.txt"));

/* 获取输入字节流的文件通道 */
FileChannel fcin = fin.getChannel();
ByteBuffer buf = ByteBuffer.allocateDirect(1024);

while (fcin.read(buf) != -1) {
    buf.flip();//转换为读模式,将limit设为pos,pos设为0,准备从buf取数据
    while(buf.hasRemaining()){//判断buf中pos是否小于limit,
        System.out.print((char) buf.get());//获取一个字节,pos+1
    }
    buf.clear();//清空buf,将limit设为分配空间48,pos设为0
}

上面是打开一个Channerl,我们直接看fcin.read(buf)这里

public int read(ByteBuffer var1) throws IOException {
    this.ensureOpen();
    if (!this.readable) {
        throw new NonReadableChannelException();
    } else {
        Object var2 = this.positionLock;
        synchronized(this.positionLock) {
            int var3 = 0;
            int var4 = -1;

            try {
                this.begin();
                var4 = this.threads.add();
                if (!this.isOpen()) {
                    byte var12 = 0;
                    return var12;
                } else {
                    do {
                        var3 = IOUtil.read(this.fd, var1, -1L, this.nd);
                    } while(var3 == -3 && this.isOpen());

                    int var5 = IOStatus.normalize(var3);
                    return var5;
                }
            } finally {
                this.threads.remove(var4);
                this.end(var3 > 0);

                assert IOStatus.check(var3);

            }
        }
    }
}

然后看关键的IOUtil.read(this.fd, var1, -1L, this.nd);

static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
    if (var1.isReadOnly()) {
        throw new IllegalArgumentException("Read-only buffer");
    } else if (var1 instanceof DirectBuffer) {
        return readIntoNativeBuffer(var0, var1, var2, var4);
    } else {
        ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());

        int var7;
        try {
            int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
            var5.flip();
            if (var6 > 0) {
                var1.put(var5);
            }

            var7 = var6;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(var5);
        }

        return var7;
    }
}

因为是DirectBuffer,所以走return readIntoNativeBuffer(var0, var1, var2, var4);这个分支

private static int readIntoNativeBuffer(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
    int var5 = var1.position();
    int var6 = var1.limit();

    assert var5 <= var6;

    int var7 = var5 <= var6 ? var6 - var5 : 0;
    if (var7 == 0) {
        return 0;
    } else {
        boolean var8 = false;
        int var9;
        if (var2 != -1L) {
            var9 = var4.pread(var0, ((DirectBuffer)var1).address() + (long)var5, var7, var2);
        } else {
            var9 = var4.read(var0, ((DirectBuffer)var1).address() + (long)var5, var7);
        }

        if (var9 > 0) {
            var1.position(var5 + var9);
        }

        return var9;
    }
}

因为传参var2是-1,所以走这个分支

int read(FileDescriptor var1, long var2, int var4) throws IOException {
    return read0(var1, var2, var4);
}

最后又是一个native方法

static native int read0(FileDescriptor var0, long var1, int var3) throws IOException;

我们在openjdk中找到FileChannelImpl.这个文件,找到read0方法

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这个方法,在fileapi.h文件中,属于windows api

API链接:ReadFile function (fileapi.h) - Win32 apps

​可以看到NIO方法实际上是直接读取到缓冲区中


最后我们做个简单的总结,IO是读入到字符数组,而NIO是使用Unsafe直接读取到缓冲区(堆外内存)中,所以,为什么NIO比IO快,其实就是堆外内存与堆内内存的一个区别,堆内内存是由JVM所管控的Java进程内存,而且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。

关于Unsafe具体可以看看这个链接

Java魔法类:Unsafe应用解析

如果有哪里不对的,欢迎补充。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值