本文转载自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
JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this,
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
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);
}
// jdk/src/share/native/java/io/io_util.c
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; // 获取记录在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, jobject this, 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
#define IO_Write handleWrite
#define IO_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)的主要流程:

  1. 检查参数是否合法(byte数组不能为空,off和len没有越界)
  2. 判断读取的长度,如果等于0直接返回0,如果大于BUF_SIZE需要在堆空间申请内存,如果`0则直接在使用栈空间的缓存
  3. 从Java空间的byte数组复制数据到中C空间的char数组中
  4. 调用write系统调用写文件内容到系统中

重要收获:

  1. 使用FileOutputStream#write(byte[], int, int)写入的长度,len一定不能大于8192!因为在小于8192时,会直接利用栈空间的char数组,如果大于,则需要调用malloc申请内存,并且还需要free释放内存,这是非常消耗时间的。
  2. 相比于直接使用系统调用,Java的写入会多一次拷贝!

关闭文件

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

总结

  • FileOutputStream打开文件使用open系统调用
  • FileOutputStream写入文件使用write系统调用
  • FileOutputStream关闭文件使用close系统调用
  • 使用FileOutputStream#write(byte[], int, int)写入的长度,len一定不能大于8192!因为在小于8192时,会直接利用栈空间的char数组,如果大于,则需要调用malloc申请内存,并且还需要free释放内存,这是非常消耗时间的。
  • 相比于直接使用系统调用,Java的写入会多一次拷贝!
  • FileOutputStream#write是无缓冲的,所以每次调用对对应一次系统调用,可能会有较低的性能,需要结合BufferedOutputStream提高性能

JDK源码阅读-FileOutputStream的更多相关文章

  1. JDK源码阅读-RandomAccessFile

    本文转载自JDK源码阅读-RandomAccessFile 导语 FileInputStream只能用于读取文件,FileOutputStream只能用于写入文件,而对于同时读取文件,并且需要随意移动 ...

  2. JDK源码阅读-FileInputStream

    本文转载自JDK源码阅读-FileInputStream 导语 FileIntputStream用于打开一个文件并获取输入流. 打开文件 我们来看看FileIntputStream打开文件时,做了什么 ...

  3. JDK源码阅读-FileDescriptor

    本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...

  4. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  5. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

  6. 利用IDEA搭建JDK源码阅读环境

    利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...

  7. JDK源码阅读-ByteBuffer

    本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...

  8. JDK源码阅读-Reference

    本文转载自JDK源码阅读-Reference 导语 Java最初只有普通的强引用,只有对象存在引用,则对象就不会被回收,即使内存不足,也是如此,JVM会爆出OOME,也不会去回收存在引用的对象. 如果 ...

  9. JDK源码阅读-DirectByteBuffer

    本文转载自JDK源码阅读-DirectByteBuffer 导语 在文章JDK源码阅读-ByteBuffer中,我们学习了ByteBuffer的设计.但是他是一个抽象类,真正的实现分为两类:HeapB ...

随机推荐

  1. vb.net和C#两兄弟谁的封装性更好?

    引言: 虽然没怎样接触过vb.net,但是大概对于vb6还是比较了解的.前者是从后者基础上发展而来的.后来接触了C#编程语言,起初没有太关心她和vb.net有啥不同的地方,话说都是面向对象的,而且都是 ...

  2. AcWing 216 Rainbow 的信号

    题意 给定一个长度为n的序列,然后从\(1\sim N\) 这 N 个数中选取两个数\(l,r\) , 如果\(l>r\),则交换\(l,r\).把第\(l\) 个数到第\(r\)个数取出来构成 ...

  3. 2019 Multi-University Training Contest 1 D.Vacation(思维)

    题意:有n俩车行驶在一条道路上,每辆车有车长li,距离终点的距离si,速度vi,不能超出,并且驶过终点后会依旧保持原状态行驶,问最后一辆车过终点的时间. 思路:因为行驶过终点后还是要保持之前的行驶方式 ...

  4. 【uva 753】A Plug for UNIX(图论--网络流最大流 Dinic)

    题意:有N个插头,M个设备和K种转换器.要求插的设备尽量多,问最少剩几个不匹配的设备. 解法:给读入的各种插头编个号,源点到设备.设备通过转换器到插头.插头到汇点各自建一条容量为1的边.跑一次最大流就 ...

  5. fzu2218 Simple String Problem

    Accept: 2    Submit: 16 Time Limit: 2000 mSec    Memory Limit : 32768 KB  Problem Description Recent ...

  6. hdu4217 Data Structure?

    Problem Description Data structure is one of the basic skills for Computer Science students, which i ...

  7. NCD 2019 H. Mr. Hamra and his quantum particles

    题意:给你n个数,有m次操作,每次使得两个数相连接,询问q次,问某两个数是否连接在一起. 题解:这其实是一道并查集的裸题,这里就不再多说了,写个路径压缩的find函数即可. 代码: #include ...

  8. WSL2 使用Docker运行.NET Core

    Docker的安装在前面说过了,此处就不说了,我们检查一下版本: 步入正题. 首先,我们为项目创建Dockerfile(无扩展名) 确保Docker是启动状态: 构建镜像,注意名称必须是全部小写(此处 ...

  9. 洛谷P1119-灾后重建-floyd算法

    洛谷P1119-灾后重建 题目描述 给出\(B\)地区的村庄数NN,村庄编号从\(0\)到\(N-1\),和所有\(M\)条公路的长度,公路是双向的. 给出第\(i\)个村庄重建完成的时间\(t_i\ ...

  10. CF1475-C. Ball in Berland

    CF1475-C. Ball in Berland 题意: 一个班级有\(a\)个男生和\(b\)个女生,现在这个班级有\(k\)对男女愿意一起出席毕业典礼,这里注意\(k\)对男女中可能会有某个男生 ...