JDK源码阅读-FileOutputStream

FileOutputStream
用户打开文件并获取输出流。

打开文件

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();
    fd.attach(this);
    this.append = append; // 记录是否是append追加模式
    this.path = name;

    open(name, append);
}

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

private native void open0(String name,boolean append)
    throws FileNotFoundException;
// jdk/src/solaris/native/java/io/FileOutputStream_md.c
JNIEXPORTvoid JNICALL
Java_java_io_FileOutputStream_open(JNIEnv *env, jobjectthis,
                                   jstring path, jboolean append) {
    // 使用O_WRONLY,O_CREAT模式打开文件,如果文件不存在会新建文件
    // 如果java中指定append参数为true,则使用O_APPEND追加模式
    // 如果java中指定append参数为false,则使用O_TRUNC模式,如果文件存在内容,会清空掉
    fileOpen(env, this, path, fos_fd,
             O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
}

fileOpen
之后的流程与 FileInputStream
的一致,可以参考 JDK源码阅读-FileInputStream

写文件

FileOutputStream
提供了三个write函数:

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

private native void write(int b, boolean append)throws IOException;

private native void writeBytes(byte b[], int off, int len, boolean append)
    throws IOException;
// jdk/src/solaris/native/java/io/FileOutputStream_md.c
JNIEXPORTvoid JNICALL
Java_java_io_FileOutputStream_write(JNIEnv *env, jobjectthis, jint byte, jboolean append) {
    writeSingle(env, this, byte, append, fos_fd);
}

JNIEXPORTvoid 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);
}
// jdk/src/share/native/java/io/io_util.c
void
writeSingle(JNIEnv *env, jobjectthis, jint byte, jboolean append, jfieldID fid) {
    // Discard the 24 high-order bits of byte. See OutputStream#write(int)
    char c = (char) byte;
    jint n;

    // 获取记录在FileDescriptor中的文件描述符
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return;
    }
    // 追加模式和普通模式使用不同的函数
    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, jobjectthis, jbyteArray bytes,
           jint off, jint len, jboolean append, jfieldID fid)
{
    jint n;
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;

    // 判断Java传入的byte数组是否是null
    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, NULL);
        return;
    }

    // 判断off和len参数是否数组越界
    if (outOfBounds(env, off, len, bytes)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return;
    }

    // 如果写入长度为0,直接返回0
    if (len == 0) {
        return;
    } else if (len > BUF_SIZE) {
        // 如果写入长度大于BUF_SIZE(8192),无法使用栈空间buffer
        // 需要调用malloc在堆空间申请buffer
        buf = malloc(len);
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return;
        }
    } else {
        buf = stackBuf;
    }

    // 复制Java传入的byte数组数据到C空间的buffer中
    (*env)->GetByteArrayRegion(env, bytes, off, len, (jbyte *)buf);

    if (!(*env)->ExceptionOccurred(env)) {
        off = 0;
        while (len > 0) {
            // 获取记录在FileDescriptor中的文件描述符
            fd = GET_FD(this, fid);
            if (fd == -1) {
                JNU_ThrowIOException(env, "Stream Closed");
                break;
            }

            // 追加模式和普通模式使用不同的函数
            if (append == JNI_TRUE) {
                n = IO_Append(fd, buf+off, len);
            } else {
                n = IO_Write(fd, buf+off, len);
            }
            if (n == -1) {
                JNU_ThrowIOExceptionWithLastError(env, "Write error");
                break;
            }
            off += n;
            len -= n;
        }
    }
    if (buf != stackBuf) {
        free(buf);
    }
}

IO_Write
/ IO_Append
虽然看起来是两个不同的函数,其实是两个不同的宏定义,指向同一个函数 handleWrite

// jdk/src/solaris/native/java/io/io_util_md.h
#defineIO_Write handleWrite
#defineIO_Append handleWrite

handleWrite
中调用 write
系统调用写入数据:

// jdk/src/solaris/native/java/io/io_util_md.c
ssize_t
handleWrite(FD fd, const void *buf, jint len)
{
    ssize_t result;
    RESTARTABLE(write(fd, buf, len), result);
    return result;
}

FileOutputStream#write(byte[], int, int)
的主要流程:

0<len<=BUF_SIZE
write

重要收获:

FileOutputStream#write(byte[], int, int)

关闭文件

public void close()throws IOException {
    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        channel.close();
    }

    fd.closeAll(new Closeable() {
        public void close()throws IOException {
            close0();
        }
    });
}

FileOutputStream
关闭文件的逻辑和 FileInputStream
关闭文件的逻辑是一样的,参考 JDK源码阅读-FileDescriptor

原文 

http://imushan.com/2018/06/04/java/language/JDK源码阅读-FileOutputStream/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JDK源码阅读-FileOutputStream

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址