目录
一、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是一个抽象类而非接口类,该类实现的接口如下:
其中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继承自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中。
initIDs本地方法是FileInputStream的静态代码块执行的,如下:
该方法用于初始化FileInputStream中fd字段的引用ID,知道此引用ID和具体的FileInputStream实例就可获取对应的fd字段引用了,如下:
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)
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));
}
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表示一个文件描述符,具体可以是一个打开的文件或者Socket或者字节序列,主要用在FileInputStream和FileOutputStream中,应用程序不应该直接创建FileDescriptor实例。Window下和Linux下FileDescriptor稍有差异,我们关注Linux下的实现,该类的源码在jdk\src\solaris\classes\java\io目录下,其包含的属性如下:
//open函数返回的文件描述符
private int fd;
private Closeable parent;
private List otherParents;
private boolean closed;
构造函数的实现如下:
该类包含了3个static final常量,分别对应到System类的in,out和err常量,如下:
重点关注以下方法的实现。
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;
}
}
}
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也是一个抽象类,该类定义的方法如下:
子类必须实现的抽象方法只有一个,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继承自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中。
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);
}
这两个方法的核心都是写入指定字节数的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;
}