Java8 FileInputStream / FileDescriptor / FileOutputStream 源碼解析

目錄

一、InputStream

二、FileInputStream

1、定義

2、initIDs 

3、 open0 / close0

4、read0/ readBytes 

5、skip0 / available0

三、FileDescriptor

1、定義

2、attach / closeAll

3、initIDs / sync

四、OutputStream

 五、FileOutputStream

1、定義

2、initIDs / open0 / close0

3、write / writeBytes


從本篇博客開始會逐一講解InputStream / OutputStream及其對應子類的使用與實現細節。

一、InputStream

      InputStream是一個抽象類而非接口類,該類實現的接口如下:

其中AutoCloseable接口是JDK 1.7引入的,try代碼塊依賴此接口實現資源自動釋放;Closeable接口是JDK 1.5引入的,覆寫了AutoCloseable定義的close方法,將其變成public並拋出IOException異常。InputStream的子類衆多,重點關注io包下的子類,如下:

其中帶紅點的都是內部私有的類,其他public子類在後續的博客中會陸續探討。

     InputStream定義的方法如下:

其中子類必須實現的抽象方法只有一個,用於讀取流中下一個字節的無參的read方法,返回值在0到255之間,如果到達流末尾了則返回-1。該方法會阻塞當前線程,直到讀取到數據,到了流末尾或者拋出了異常,其定義如下:

 重載的兩個read方法和skip方法都是基於無參的read方法實現,其他方法都是無意義的空實現,如下:

public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

//從流中讀取字節數據,將讀取到的第一個字節到b的off處,然後依次讀取len個,返回實際讀取的字節數
public int read(byte b[], int off, int len) throws IOException {
        //校驗參數
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) { //流終止
            return -1;
        }
        //返回值在0到255之間,所以可以強轉成byte
        b[off] = (byte)c;

        int i = 1;
        try {
            //i從1開始,讀取len-1個字節
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

//跳過指定的字節數,返回實際跳過的字節數
public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }
        //MAX_SKIP_BUFFER_SIZE的值是2048
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            //讀取指定的字節數
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {//流終止,退出循環
                break;
            }
            //計算剩餘的需要跳過的字節數
            remaining -= nr;
        }

        return n - remaining;
    }

二、FileInputStream

1、定義

       FileInputStream繼承自InputStream,包含的屬性如下:

    /* 文件描述符 */
    private final FileDescriptor fd;

    /**
     * 文件路徑
     */
    private final String path;
    
    /*
     * NIO使用的FileChannel
     */
    private FileChannel channel = null;

    private final Object closeLock = new Object();

    //文件是否已關閉的標識
    private volatile boolean closed = false;

 其構造方法如下:

public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }

public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //檢查文件訪問權限
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) { //檢查路徑是否合法
            throw new FileNotFoundException("Invalid file path");
        }
        //初始化fd
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        //打開文件描述符
        open(name);
    }

//適用於從已經打開的fdObj中讀取數據,即多個FileInputStream實例共享一個fdObj
//可以通過getFD方法獲取當前綁定的FileDescriptor實例
public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;

        /*
         *將fd同當前File綁定
         */
        fd.attach(this);
    }

private void open(String name) throws FileNotFoundException {
        //本地方法實現
        open0(name);
    }

 FileInputStream改寫了父類所有方法的實現,其核心都通過本地方法實現,如下:

public int read() throws IOException {
        //本地方法
        return read0();
    }

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);
    }

public long skip(long n) throws IOException {
        //本地方法
        return skip0(n);
    }

public int available() throws IOException {
        //本地方法
        return available0();
    }

public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                //獲取鎖,如果已關閉則返回
                return;
            }
            //未關閉,將closed置爲true
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               //本地方法
               close0();
           }
        });
    }

 下面逐一說明各本地方法的實現細節,位於OpenJDK jdk\src\share\native\java\io\FileInputStream.c中,FileInputStream.c中文件操作相關方法的實現在jdk\src\solaris\native\java\io\io_util_md.c中。

2、initIDs 

      initIDs本地方法是FileInputStream的靜態代碼塊執行的,如下:

該方法用於初始化FileInputStream中fd字段的引用ID,知道此引用ID和具體的FileInputStream實例就可獲取對應的fd字段引用了,如下:

3、 open0 / close0

       open0的核心就是打開文件描述符的open64函數,返回的fd是一個數字,當前未使用的最小的文件描述符,實際是當前進程打開文件的文件指針數組的索引,一般從3開始,因爲標準輸入流0,標準輸出流1,標準錯誤流2是Linux預分配的。close0的核心就是關閉文件描述符的close函數。

JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open0(JNIEnv *env, jobject this, jstring path) {
    fileOpen(env, this, path, fis_fd, O_RDONLY);
}

JNIEXPORT void JNICALL
Java_java_io_FileInputStream_close0(JNIEnv *env, jobject this) {
    fileClose(env, this, fis_fd);
}


void
fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
{
    WITH_PLATFORM_STRING(env, path, ps) {
        FD fd;

#if defined(__linux__) || defined(_ALLBSD_SOURCE)
        /* Remove trailing slashes, since the kernel won't */
        char *p = (char *)ps + strlen(ps) - 1;
        while ((p > ps) && (*p == '/'))
            *p-- = '\0';
#endif
        //打開文件描述符
        fd = handleOpen(ps, flags, 0666);
        if (fd != -1) {
            //fid就是FileInputStream實例的fd字段的jfieldID
            //保存fd到FileInputStream的fd屬性對應的FileDescriptor實例的fd字段中
            SET_FD(this, fd, fid);
        } else {
            throwFileNotFoundException(env, path);
        }
    } END_PLATFORM_STRING(env, ps);
}

void
fileClose(JNIEnv *env, jobject this, jfieldID fid)
{
    //獲取當前FileInputStream實例的fd屬性對應的FileDescriptor實例fd字段的值
    FD fd = GET_FD(this, fid);
    if (fd == -1) { //爲-1,說明文件未打開
        return;
    }

    /* 
    實際關閉前先將其置爲-1,可減少執行關閉動作時其他線程仍在訪問該fd
     */
    SET_FD(this, -1, fid);

    /*
     * 如果是標準輸入流0,標準輸出流1,標準錯誤流2則不關閉他們,而是將其重定向到/dev/null
     */
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
        //打開/dev/null的文件描述符
        int devnull = open("/dev/null", O_WRONLY);
        if (devnull < 0) {
            SET_FD(this, fd, fid); // restore fd
            JNU_ThrowIOExceptionWithLastError(env, "open /dev/null failed");
        } else {
            //將fd重定向到devnull,實際是將devnull對應的文件描述符拷貝到fd處
            dup2(devnull, fd);
            //關閉devnull對應的文件描述符
            close(devnull);
        }
    } else if (close(fd) == -1) { //非標準流
        JNU_ThrowIOExceptionWithLastError(env, "close failed");
    }
}

FD
handleOpen(const char *path, int oflag, int mode) {
    FD fd;
    //調用open64函數打開文件描述符,返回的fd是一個數字,當前未使用的最小的文件描述符,實際是一個當前進程打開文件的文件指針數組的索引
    RESTARTABLE(open64(path, oflag, mode), fd);
    if (fd != -1) {
        struct stat64 buf64;
        int result;
        //調用fstat64函數獲取文件屬性
        RESTARTABLE(fstat64(fd, &buf64), result);
        if (result != -1) {
            //如果目標文件是文件夾則關閉文件描述符
            if (S_ISDIR(buf64.st_mode)) {
                //調用close函數關閉fd
                close(fd);
                errno = EISDIR;
                fd = -1;
            }
        } else {
            //調用fstat64函數失敗,關閉文件描述符
            close(fd);
            fd = -1;
        }
    }
    return fd;
}

#define SET_FD(this, fd, fid) \
    //fid是FileInputStream實例的fd字段的jfieldID,即先獲取FileInputStream實例的fd屬性
    if ((*env)->GetObjectField(env, (this), (fid)) != NULL) \
        //IO_fd_fdID是FileDescriptor實例fd字段的jfieldID
        (*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))

#define GET_FD(this, fid) \
    //如果FileInputStream實例的fd屬性爲空,則返回-1,不爲空則返回該屬性對應的FileDescriptor實例fd字段的值
    (*env)->GetObjectField(env, (this), (fid)) == NULL ? \ 
        -1 : (*env)->GetIntField(env, (*env)->GetObjectField(env, (this), (fid)), IO_fd_fdID)

4、read0/ readBytes 

      read0用於讀取單個字節,readBytes 是讀取多個字節的數據並寫入數組中指定位置,這兩個方法底層都是依賴可讀取多個字節的read函數,該函數返回實際讀取的字節數,如果爲0,則表示已經讀取到流末尾。

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read0(JNIEnv *env, jobject this) {
    return readSingle(env, this, fis_fd);
}

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);
}

jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
    jint nread;
    char ret;
    //獲取關聯的文件描述符
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        //爲-1,描述符已關閉
        JNU_ThrowIOException(env, "Stream Closed");
        return -1;
    }
    //讀取單個字節,讀取的結果保存在ret中
    //IO_Read通過宏定義指向handleRead方法
    nread = IO_Read(fd, &ret, 1);
    if (nread == 0) { /* EOF */
        return -1; //讀取結束
    } else if (nread == -1) { /* IO異常 */
        JNU_ThrowIOExceptionWithLastError(env, "Read error");
    }
    //求且,保證ret的值不超過255
    return ret & 0xFF;
}

jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
          jint off, jint len, jfieldID fid)
{
    jint nread;
    //BUF_SIZE是一個宏,取值爲8192
    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 {
        //正常使用stackBuf
        buf = stackBuf;
    }
    
    //獲取當前文件關聯的fd
    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) {
            //讀取成功,將數據寫入到bytes數組中
            (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
        } else if (nread == -1) {
            //read函數讀取失敗
            JNU_ThrowIOExceptionWithLastError(env, "Read error");
        } else { /* EOF */
            nread = -1; //讀取到流末尾
        }
    }

    if (buf != stackBuf) {
        free(buf);//釋放緩存
    }
    return nread;
}

ssize_t
handleRead(FD fd, void *buf, jint len)
{
    ssize_t result;
    //調用read函數讀取字節數據,buf用於保存數據,len表示讀取的字節數
    //result用於保存read函數的調用結果,即實際讀取的字節數,如果爲0,則表示已經到達流末尾了
    RESTARTABLE(read(fd, buf, len), result);
    return result;
}

#define IS_NULL(obj) ((obj) == NULL)

static int
outOfBounds(JNIEnv *env, jint off, jint len, jbyteArray array) {
    return ((off < 0) ||
            (len < 0) ||
            //len超過了array中剩餘可用空間
            ((*env)->GetArrayLength(env, array) - off < len));
}

5、skip0 / available0

      skip0的核心是用於獲取和操作文件讀寫位置的lseek函數,available0的實現會區分fd的類型,如果是Socket等文件描述符,則使用ioctl函數獲取接受緩衝區中的字節數,如果是常規的文件,則使用lseek函數獲取剩餘的未讀取的字節數。

JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_skip0(JNIEnv *env, jobject this, jlong toSkip) {
    jlong cur = jlong_zero;
    jlong end = jlong_zero;
    
    //獲取當前文件關聯的文件描述符
    FD fd = GET_FD(this, fis_fd);
    if (fd == -1) {
        //文件描述符已關閉,拋出異常
        JNU_ThrowIOException (env, "Stream Closed");
        return 0;
    }
    //IO_Lseek通過宏定義指向lseek64函數
    //獲取當前文件的讀寫位置
    if ((cur = IO_Lseek(fd, (jlong)0, (jint)SEEK_CUR)) == -1) {
        //調用函數失敗,拋出異常
        JNU_ThrowIOExceptionWithLastError(env, "Seek error");
    //將當前讀寫位置向後移動toSkip字節,返回當前的字節位置    
    } else if ((end = IO_Lseek(fd, toSkip, (jint)SEEK_CUR)) == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "Seek error");
    }
    //返回實際的跳過字節數,因爲跳過的字節數可能超過文件末尾
    return (end - cur);
}

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_available0(JNIEnv *env, jobject this) {
    jlong ret;
     //獲取當前文件關聯的文件描述符
    FD fd = GET_FD(this, fis_fd);
    if (fd == -1) {
        //文件描述符已關閉,拋出異常
        JNU_ThrowIOException (env, "Stream Closed");
        return 0;
    }
    //IO_Available通過宏定義指向handleAvailable方法
    if (IO_Available(fd, &ret)) {
        if (ret > INT_MAX) {
            //返回值超過int的最大值,則將其置爲int的最大值
            ret = (jlong) INT_MAX;
        } else if (ret < 0) {
            ret = 0;
        }
        //long轉換成int類型
        return jlong_to_jint(ret);
    }
    //調用C函數失敗,拋出異常
    JNU_ThrowIOExceptionWithLastError(env, NULL);
    return 0;
}

jint
handleAvailable(FD fd, jlong *pbytes)
{
    int mode;
    struct stat64 buf64;
    jlong size = -1, current = -1;

    int result;
    //調用fstat64函數獲取文件描述符的屬性
    RESTARTABLE(fstat64(fd, &buf64), result);
    if (result != -1) {
        mode = buf64.st_mode;
        if (S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) {
            int n;
            int result;
            //ioctl函數是對IO管道進行管理的函數,FIONREAD是該函數支持的一個cmd,表示獲取接受數據緩衝區中的字節數
            //result用於保存函數執行的結果,n用於保存cmd執行的結果
            RESTARTABLE(ioctl(fd, FIONREAD, &n), result);
            if (result >= 0) {
                //執行成功,可用字節數就是n
                *pbytes = n;
                return 1;
            }
        } else if (S_ISREG(mode)) {
            //如果是常規的文件,則size是文件大小
            size = buf64.st_size;
        }
    }
    
    //獲取當前的文件讀寫位置
    if ((current = lseek64(fd, 0, SEEK_CUR)) == -1) {
        //獲取失敗,則返回0
        return 0;
    }

    if (size < current) {
        //如果size小於當前讀寫位置,說明實際的文件大小不止size
        //則獲取文件末尾對應的位置
        if ((size = lseek64(fd, 0, SEEK_END)) == -1)
            return 0;
        //上一步操作成功會將文件讀寫位置移動到文件末尾,此處將其恢復至原來的位置    
        else if (lseek64(fd, current, SEEK_SET) == -1)
            return 0;
    }
    //計算剩餘的流數據大小
    *pbytes = size - current;
    return 1;
}

三、FileDescriptor

1、定義

     FileDescriptor表示一個文件描述符,具體可以是一個打開的文件或者Socket或者字節序列,主要用在FileInputStream和FileOutputStream中,應用程序不應該直接創建FileDescriptor實例。Window下和Linux下FileDescriptor稍有差異,我們關注Linux下的實現,該類的源碼在jdk\src\solaris\classes\java\io目錄下,其包含的屬性如下:

    //open函數返回的文件描述符
    private int fd;

    private Closeable parent;
    private List<Closeable> otherParents;
    private boolean closed;

 構造函數的實現如下:

該類包含了3個static final常量,分別對應到System類的in,out和err常量,如下:

重點關注以下方法的實現。

2、attach / closeAll

      attach方法用於保存從當前FileDescriptor實例中讀取數據的Closeable實例引用,方便在closeAll方法中一次的將所有從該FileDescriptor實例中讀取數據的Closeable實例都關閉掉。

//將某個FileInputStream實例同當前fd綁定,即存在多個FileInputStream同時從一個fd中讀取數據
//主要爲下面的closeAll服務
synchronized void attach(Closeable c) {
        if (parent == null) {
            // first caller gets to do this
            parent = c;
        } else if (otherParents == null) {
            //初始化otherParents
            otherParents = new ArrayList<>();
            otherParents.add(parent);
            otherParents.add(c);
        } else {
            otherParents.add(c);
        }
    }

//將多個從當前fd中讀取數據的Closeable實例關閉
@SuppressWarnings("try")
synchronized void closeAll(Closeable releaser) throws IOException {
        if (!closed) {
            closed = true;
            IOException ioe = null;
            try (Closeable c = releaser) {
                if (otherParents != null) {
                    for (Closeable referent : otherParents) {
                        try {
                            //調用close方法關閉流
                            referent.close();
                        } catch(IOException x) {
                            if (ioe == null) {
                                ioe = x;
                            } else {
                                //保存異常信息
                                ioe.addSuppressed(x);
                            }
                        }
                    }
                }
            } catch(IOException ex) {
                /*
                 * If releaser close() throws IOException
                 * add other exceptions as suppressed.
                 */
                if (ioe != null)
                    ex.addSuppressed(ioe);
                ioe = ex;
            } finally {
                if (ioe != null) //拋出異常
                    throw ioe;
            }
        }
    }

3、initIDs / sync

      initIDs是本地方法,在static代碼塊中調用,如下:

該方法用來初始化FileDescriptor類中fd屬性的引用ID,其實現如下:

sync也是一個本地方法,其實現如下:

JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_sync(JNIEnv *env, jobject this) {
    FD fd = THIS_FD(this);
    //IO_Sync通過宏定義指向fsync函數,該函數會將高速緩存中的發生修改的塊緩衝區和文件屬性的修改強制刷新到磁盤中
    //會阻塞當前線程直到刷新動作完成
    if (IO_Sync(fd) == -1) {
        //調用fsync失敗,拋出異常
        JNU_ThrowByName(env, "java/io/SyncFailedException", "sync failed");
    }
}

#define THIS_FD(obj) (*env)->GetIntField(env, obj, IO_fd_fdID)

四、OutputStream

     OutputStream也是一個抽象類,該類定義的方法如下:

子類必須實現的抽象方法只有一個,write(int)方法,用於寫入單個字節,與InputStream中的無參read方法對應;flush和close方法是空實現,另外兩個重載的write方法都是依賴第一個write方法實現,如下:

public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            //數組越界       
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        //遍歷字節數組,逐一寫入
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

 五、FileOutputStream

1、定義

      FileOutputStream繼承自OutputStream,包含的屬性如下:

    /**
     * 文件描述符
     */
    private final FileDescriptor fd;

    /**
     * 是否寫入到文件後面,默認爲false,會文件中原來的內容清空
     */
    private final boolean append;

    /**
     * 關聯的FileChannel
     */
    private FileChannel channel;

    /**
     * 文件路徑
     */
    private final String path;
    
    //流關閉用到的鎖
    private final Object closeLock = new Object();
    //是否關閉
    private volatile boolean closed = false;

 其構造方法如下:

public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //檢查是否訪問權限
            security.checkWrite(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) { //檢查路徑是否合法
            throw new FileNotFoundException("Invalid file path");
        }
        //初始化文件描述符
        this.fd = new FileDescriptor();
        //將當前FileOutputStream實例註冊到fd中
        fd.attach(this);
        this.append = append;
        this.path = name;
        //打開文件描述符
        open(name, append);
    }

private void open(String name, boolean append)
        throws FileNotFoundException {
        open0(name, append);
    }

同FileInputStream,write和close相關方法的實現都封裝在本地方法中,如下:

public void write(int b) throws IOException {
        //本地方法
        write(b, append);
    }

public void write(byte b[]) throws IOException {
        //本地方法
        writeBytes(b, 0, b.length, append);
    }

public void write(byte b[], int off, int len) throws IOException {
        //本地方法
        writeBytes(b, off, len, append);
    }

public void close() throws IOException {
        synchronized (closeLock) { //獲取鎖
            if (closed) {
                return; //已關閉則直接返回
            }
            closed = true;//未關閉,將close的置爲true
        }

        if (channel != null) {
            channel.close();
        }
        //關閉fd
        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();//本地方法
           }
        });
    }

注意FileOutputStream並沒有改寫父類OutputStream中空實現的flush方法,如果希望將文件修改顯示的刷新到磁盤,需要顯示調用FileDescriptor的sync方法,底層是調用fsync函數。下面逐一說明各本地方法的實現細節,源碼在OpenJDK jdk\src\solaris\native\java\io\FileOutputStream_md.c中。

2、initIDs / open0 / close0

      initIDs本地方法是在靜態代碼塊中執行的,如下:

該方法用於初始化 FileOutputStream中fd字段的引用ID,如下:

 open0 和 close0 的實現跟中open0 和 close0方法的實現完全一樣,這裏不再詳細展開,如下:

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_open0(JNIEnv *env, jobject this,
                                    jstring path, jboolean append) {
    //FileInputStream中調用fileOpen方法時,傳遞的flag是O_RDONLY                                 
    fileOpen(env, this, path, fos_fd,
             O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
}

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_close0(JNIEnv *env, jobject this) {
    fileClose(env, this, fos_fd);
}

3、write / writeBytes

        這兩個方法的核心都是寫入指定字節數的write函數,如下:

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_write(JNIEnv *env, jobject this, jint byte, jboolean append) {
    writeSingle(env, this, byte, append, fos_fd);
}

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_writeBytes(JNIEnv *env,
    jobject this, jbyteArray bytes, jint off, jint len, jboolean append) {
    writeBytes(env, this, bytes, off, len, append, fos_fd);
}

void
writeSingle(JNIEnv *env, jobject this, jint byte, jboolean append, jfieldID fid) {
    // Discard the 24 high-order bits of byte. See OutputStream#write(int)
    char c = (char) byte;
    jint n;
    //獲取關聯的fd
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return;
    }
    //IO_Append和IO_Write 都通過宏指向handleWrite函數
    if (append == JNI_TRUE) {
        n = IO_Append(fd, &c, 1);
    } else {
        n = IO_Write(fd, &c, 1);
    }
    if (n == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "Write error");
    }
}

void
writeBytes(JNIEnv *env, jobject this, jbyteArray bytes,
           jint off, jint len, jboolean append, jfieldID fid)
{
    jint n;
    //BUF_SIZE的值是8192
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;

    if (IS_NULL(bytes)) { //字節數組爲null,拋出異常
        JNU_ThrowNullPointerException(env, NULL);
        return;
    }

    if (outOfBounds(env, off, len, bytes)) {//數組越界
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return;
    }

    if (len == 0) {
        return;
    } else if (len > BUF_SIZE) {
        //超過BUF_SIZE,則另外分配內存
        buf = malloc(len);
        if (buf == NULL) {
            //內存分配失敗,拋出異常
            JNU_ThrowOutOfMemoryError(env, NULL);
            return;
        }
    } else {
        buf = stackBuf;
    }
    
    //將bytes數組中的數據拷貝到buf中
    (*env)->GetByteArrayRegion(env, bytes, off, len, (jbyte *)buf);

    if (!(*env)->ExceptionOccurred(env)) {
        //上一步的拷貝執行成功
        off = 0;
        while (len > 0) {
            fd = GET_FD(this, fid);
            if (fd == -1) {
                JNU_ThrowIOException(env, "Stream Closed");
                break;
            }
            //IO_Append和IO_Write 都通過宏指向handleWrite函數
            //buf+off把指針往前移動off個
            if (append == JNI_TRUE) {
                //會一次性最多寫入len個,實際因爲多種原因可能沒有寫入len個就返回了
                //n表示寫入的字節數
                n = IO_Append(fd, buf+off, len);
            } else {
                n = IO_Write(fd, buf+off, len);
            }
            if (n == -1) {
                //寫入失敗,拋出異常
                JNU_ThrowIOExceptionWithLastError(env, "Write error");
                break;
            }
            //如果執行成功,n等於實際寫入的字節數
            off += n;
            len -= n;
        }
    }
    if (buf != stackBuf) {
        free(buf); //釋放單獨分配的內存
    }
}

ssize_t
handleWrite(FD fd, const void *buf, jint len)
{
    ssize_t result;
    //調用write函數寫入指定的字節數
    RESTARTABLE(write(fd, buf, len), result);
    return result;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章