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