JNI 探秘 — FileInputStream 的 read 方法詳解

2021-03-02 ImportNew

(點擊上方公眾號,可快速關注)

來源:左瀟龍 ,

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技能

相關焦點

  • JNI 探秘 — 你不知道的 FileInputStream 的秘密
    (點擊上方公眾號,可快速關注)來源:Java paradise ,www.cnblogs.com/zuoxiaolong/p/jni1
  • JNI-Thread中start方法調用與run方法回調分析
    (); jni.jniHello(); }}在這個類中,我們定義了一個jniHello的native方法,然後在main方法中對其進行調用。我們查看該.h文件,其中就包含了jniHello方法的定義,當然需要注意到的是,這裡的方法名和.h文件本身的命名是jni根據我們類的包名和類名確定出來的,不能修改。
  • java基礎之理解JNI原理
    /com/magc/jni/HelloWorld.java在該HelloWorld.java所在目錄下生成HelloWorld.class然後使用javah生成頭文件,命令:javah -jni com.magc.jni.HelloWorld在當前目錄下生成com_magc_jni_HelloWorld.h頭文件,此文件供C、C++程序來引用並實現其中的函數
  • JNI基本數據類型
    在Java與native間傳遞基本數據類型是十分簡單而又直接的,在Java的native方法的參數和返回值,在生成的頭文件的函數籤名中使用上一節介紹的對應的native類型進行替換,在C/C++的實現中直接使用即可。實例我們來看一個實例。
  • Android JNI中的異常處理 與Log日誌使用2步驟
    而在jni 中使用日誌就只需要2步驟。步驟一:引入頭文件 ,include log.h步驟二 定義宏。結果:JNI異常處理異常處理是java 程序設計中的重要功能,java 中 拋出一個異常,虛擬機停止執行代碼並且調用棧反向檢查能處理特定的異常類型處理程序代碼塊,叫做捕獲異常。
  • JVM 解剖公園:JNI 臨界區與 GC Locker
    理論熟悉 JNI 的人知道有兩組讀取數組內容的方法,包括 `Get<PrimitiveType>Array*` [系列][4]:>>>void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);   void
  • Java通過-jni調用c語言
    TestJNI test=new TestJNI();             test.set(1);             System.out.println(test.get());                  }}      (2)用javac編譯TestJNI.java文件生成TestJNI.class文件(3)用javah帶-jni
  • 迅為開發板安卓JNI開發實戰教程使用編譯好的 so 庫
    我們打開 AS,然後新建一個項目,選擇一個空的 Activity,如下圖所示:這裡我要注意一下,我們的包名要和我們調用的 jni 庫的包名一樣,否則會出問題,之前我們在寫 jni 的時候,我們的包名如下圖所示:包名:那麼我們在新建工程的時候包名也要是這個
  • Pandas數據清洗系列:read_csv函數詳解(三)
    接上一篇文章:Pandas數據清洗系列:read_csv函數詳解(二)我們學習read_csv函數中剩下的參數。這個參數如果為True,那麼read_csv將返回一個 TextFileReader 對象,而不是DataFrame。我們調用TextFileReader對象的get_chunk方法,就可以設置每次讀多少行數據。
  • Pandas數據清洗系列:read_csv函數詳解(一)
    pandas中提供了read_csv函數來讀取csv文件,今天我們來學習這個函數。read_csv函數詳解首先,我們先看一下read_csv函數有哪些參數(pandas版本號為1.2.1):pd.read_csv(    filepath_or_buffer: Union[str, pathlib.Path, IO[~AnyStr]],    sep
  • 區別readonly和const的使用方法
    後,本文繼續講《編寫高質量代碼改善C#程序的157個建議》一書第6個建議「區別readonly和const的使用方法」。喜歡本書請到各大商城購買原書,支持正版。很多初學者分不清readonly和 const的使用場合。在我看來,要使用const的理由只有一個,那就是效率。
  • 每日一課 | Java DataInputStream readByte()方法與示例
    readByte()方法在java.io包中可用。readByte()方法用於讀取和返回帶符號的輸入字節值。readByte()方法是一種非靜態方法,只能通過類對象訪問,如果嘗試使用類名稱訪問該方法,則會收到錯誤消息。readByte()方法在讀取字節時可能會引發異常。
  • Pandas數據清洗系列:read_csv函數詳解(二)
    接上一篇文章:Pandas數據清洗系列:read_csv函數詳解(一)。我們繼續學習read_csv函數中的參數。在介紹剩下參數之前,為了方便查看,我們還是先將完整的read_csv參數列出(pandas版本號為1.2.1):pd.read_csv(    filepath_or_buffer: Union[str, pathlib.Path, IO[~AnyStr]],    sep=',',    delimiter=None
  • AndroidNDK——makefile語法詳解
    一、編譯流程詳解編譯流程1、預處理完成宏替換、文件引入,以及去除空行、注釋等,為下一步的編譯做準備;也就是對各種預處理命令進行處理
  • Android JNI 之 Bitmap 操作
    在 Android 通過 JNI 去調用 Bitmap,通過 CMake 去編 so 動態連結庫的話,需要添加 jnigraphics 圖像庫。1target_link_libraries( # Specifies the target library.
  • FileInputStream和FileOutputStream詳解
    (FileDescriptor fd) FileInputStream(String path) int available() void close() FileChannel getChannel() final FileDescriptor getFD() int read
  • 如果你也用「pandas.read_csv」處理文本,這幾個參數你應該掌握
    我相信使用pandas的同學們,基本都會用到這個方法:read_csv()。這個方法確實簡單易用,但是內置參數特別多,所以今天就分享下這個方法的一些使用技巧。01基礎使用只需要給一個文件路徑即可df = pandas.read_csv("filepath")接下來開始詳細介紹幾個比較有用的參數
  • 音視頻開發之旅(17) JNI與NDK的學習和使用
    二、Java和Native交互流程JNI在Java類中通過native關鍵字聲明Native方法javac命令編譯Java類得到class文件通過javah命令(javah -jni class名稱)導出JNI的頭文件(.h文件)實現native方法編譯生成動態庫(.so文件)實現Java和C、CPP的相互調用
  • R語言數據導入之read.table
    每個不同類型的文件導入R似乎都需要不同的函數,然後就迷失在眾多的函數參數中。長話短說,通過本文這些都可以相當輕鬆,隨時把不同的函數靈活使用,不論你是初學者還是更有經驗的用戶。為了滿足這些需求,本文列舉了幾個較為簡單的數據快速導入的方法,從最簡單的文本文檔到spss和sas的文檔。請繼續閱讀後面的內容。你的數據導入數據前,需要獲取數據,如何獲取數據不在本文的探討範圍內。
  • R包readxl 的libxls讀錯誤的解決方法
    R包readxl 的libxls讀取錯誤解決方法緣起:xls轉化為xlsxCCK8和MTT實驗完成後,需要進行數據分析,自然而然想到用R批量處理