首先我们来看下传统的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具体可以看看这个链接
如果有哪里不对的,欢迎补充。