(點擊上方公眾號,可快速關注)
來源:左瀟龍 ,
www.cnblogs.com/zuoxiaolong/p/jni2.html
如有好文章投稿,請點擊 → 這裡了解詳情
上一章我們已經分析過FileInputStream的構建過程,接下來我們就來看一下read方法的讀取過程。
我們先來看下FileInputStream中的四個有關read的方法的源碼,如下。
public native int read() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
可以看到,其中有兩個本地方法,兩個不是本地方法,但是還是內部還是調用的本地方法,那麼我們研究的重點就是這兩個本地方法到底是如何實現的。
下面是這兩個本地方法的源碼,非常簡單,各位請看。
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {
return readSingle(env, this, fis_fd);//每一個本地的實例方法默認的兩個參數,JNI環境與對象的實例
}
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len) {//除了前兩個參數,後三個就是readBytes方法傳遞進來的,字節數組、起始位置、長度三個參數
return readBytes(env, this, bytes, off, len, fis_fd);
}
可以看到,這兩個本地方法的實現只是將任務又轉給了兩個方法,readSingle和ReadBytes,請注意,在調用這兩個方法時,除了常用的env和this對象,以及從JAVA環境傳過來的參數之外,還多了一個參數fis_fd,這個對象就是上一章中FileInputStream類中的fd屬性的地址偏移量了。
那麼下面我們首先來看readSingle方法的實現,如下。
/*
env和this參數就不再解釋了
fid就是FileInputStream類中fd屬性的地址偏移量
通過fid和this實例可以獲取FileInputStream類中fd屬性的內存地址
*/
jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
jint nread;//存儲讀取後返回的結果值
char ret;//存儲讀取出來的字符
FD fd = GET_FD(this, fid);//這個獲取到的FD其實就是之前handle屬性的值,也就是文件的句柄
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;//如果文件句柄等於-1,說明文件流已關閉
}
nread = (jint)IO_Read(fd, &ret, 1);//讀取一個字符,並且賦給ret變量
//以下根據返回的int值判斷讀取的結果
if (nread == 0) { /* EOF */
return -1;//代表流已到末尾,返回-1
} else if (nread == JVM_IO_ERR) { /* error */
JNU_ThrowIOExceptionWithLastError(env, "Read error");//IO錯誤
} else if (nread == JVM_IO_INTR) {
JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);//被打斷
}
return ret & 0xFF;//與0xFF做按位的與運算,去除高於8位bit的位
}
可以看到,這個方法其實最關鍵的就是IO_Read這個宏定義的處理,而IO_Read其實只是代表了一個方法名稱叫handleRead,我們去看一下handleRead的源碼。
/*
fd就是handle屬性的值
buf是收取讀取內容的數組
len是讀取的長度,可以看到,這個參數傳進來的是1
函數返回的值代表的是實際讀取的字符長度
*/
JNIEXPORT
size_t
handleRead(jlong fd, void *buf, jint len)
{
DWORD read = 0;
BOOL result = 0;
HANDLE h = (HANDLE)fd;
if (h == INVALID_HANDLE_VALUE) {//如果句柄是無效的,則返回-1
return -1;
}
//這裡ReadFile又是一個現有的函數,和上一章的CreateFile是一樣的
//都是WIN API的函數,可以百度搜索它的作用與參數詳解,理解它並不難
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) {//如果沒讀取出來東西,則判斷是到了文件末尾返回0,還是報錯了返回-1
int error = GetLastError();
if (error == ERROR_BROKEN_PIPE) {
return 0; /* EOF */
}
return -1;
}
return read;
}
到此,基本上就完全看完了無參數的read方法的源碼,它的原理其實很簡單,就是利用handle這個句柄,使用ReadFile的WIN API函數讀取了一個字符,不過值得注意的是,這些都是windows系統下的實現方式,所以不可認為這些源碼代表了所有系統下的情況。
然而對於帶有參數的read方法,其原理與無參read方法是一樣的,而且最終也是調用的handleRead這個方法,只是讀取的長度不再是1而已。
由此可以看出,文件輸入流只是已只讀方式打開了一個文件流,而且這個文件流只能依次向後讀取,因為在之前的設計模式系列裝飾器模式一文中,LZ已經提到過,對於FileInputStream進行包裝而支持回退,標記重置等操作的輸入流,都只是在內存裡創建緩衝區造成的假象,我們真正的文件輸入流是不支持這些操作的。
好了,有關FileInputstream的源碼內容就分享到此了,如果有興趣的猿友,可以繼續看一下其它的本地方法是如何實現的。
看完本文有收穫?請轉發分享給更多人
關注「ImportNew」,提升Java技能