这里以文件输入输出流:FileInputStream、FileOutputStream来进行解释。由继承关系得知,这两个输入输出类继承自InputStream和OutputStream这两个基础的输入、输出的抽象类,这时我们可以看到当我们需要读写文件的时候,就需要创建两个流对象。原理图如下:
由图中可以知道,OS提供了API的接口给用户程序调用,这时我们可以将API接口和OS,比喻成C/S,也即应用程序就是浏览器端或者桌面端程序,OS和API等价于服务器,也即Controller接口。
public class FileInputStream extends InputStream{}
public class FileOutputStream extends OutputStream{}
1、输入流源码:
// 只以文件流为例
public abstract class InputStream implements Closeable {
public int read(byte b[]) throws IOException {
return read(b, 0, b.length); // 直接调用read(byte b[], int off, int len)函数
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) { // 校验byte数组是否为空
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) { // 校验读取范围是否正确
throw new IndexOutOfBoundsException();
} else if (len == 0) { // 校验读取长度
return 0;
}
// 调用read()函数读入一个字节
int c = read();
if (c == -1) { // 验证字节是否到达了文件的末尾
return -1;
}
b[off] = (byte)c; // 将该字节数据保存到b数组中
int i = 1;
try {
// 将文件的数据,逐字节的从磁盘中读入放入b字节数组中
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
流程如下:
- read(byte b[]) 直接调用read(byte b[], int off, int len)函数
- 校验byte数组是否为空
- 校验读取范围是否正确
- 校验读取长度
- 调用read()函数读入一个字节
- 验证字节是否到达了文件的末尾
- 将该字节数据保存到b数组中
- 循环将文件的数据,逐字节的从磁盘中读入放入b字节数组中
2、输出流源码
public abstract class OutputStream implements Closeable, Flushable {
public void write(byte b[]) throws IOException {
write(b, 0, b.length); // 直接调用write(byte b[], int off, int len)函数
}
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) { // 校验写入长度是否为0
return;
}
// 循环将b数组中的数据,逐个字节写入文件
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
}
流程如下:
- write(byte b[])直接调用write(byte b[], int off, int len)函数
- 校验数组不能为空
- 校验写入范围是否合理
- 校验写入长度是否为0
- 循环将b数组中的数据,逐个字节写入文件
3,FileInputStream和FileOutputStream复写抽象类方法原理
public class FileInputStream extends InputStream{
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length); // 直接调用read(byte b[], int off, int len)方法
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len); // 直接调用readBytes(byte b[], int off, int len)方法
}
// 可以看到该方法为native,称之为JNI(Java Native Interface)方法
private native int readBytes(byte b[], int off, int len) throws IOException;
}
FileInputStream流程如下:
- read(byte b[]) 方法直接调用read(byte b[], int off, int len)方法
- read(byte b[], int off, int len)方法直接调用readBytes(byte b[], int off, int len)方法
- 可以看到readBytes方法为native,称之为JNI(Java Native Interface)方法
public class FileOutputStream extends OutputStream{
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append); // 直接调用write(byte b[], int off, int len)方法
}
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append); // 直接调用writeBytes(byte b[], int off, int len, boolean append)方法
}
// 可以看到该方法为native,称之为JNI(Java Native Interface)方法
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
}
FileOutputStream流程如下:
- write(byte b[])方法直接调用write(byte b[], int off, int len)方法
- write(byte b[], int off, int len)方法直接调用writeBytes(byte b[], int off, int len, boolean append)方法
- 可以看到writeBytes(byte b[], int off, int len, boolean append)方法为native,称之为JNI(Java Native Interface)方法
4,readBytes方法与writeBytes底层实现原理
readBytes方法
jint readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
jint off, jint len, jfieldID fid){
...
if (len == 0) {
return 0;
} else if (len > BUF_SIZE) {
buf = malloc(len); // 分配一片空间
}
...
if (fd == -1) {
...
} else {
nread = IO_Read(fd, buf, len); // 调用read函数读取数据
if (nread > 0) {
// 将数据保存放入堆内存的bytes数组中
(*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
}
...
}
...
}
关键流程如下:
- malloc分配一片空间
- 调用read函数读取数据
- 将数据保存放入堆内存的bytes数组中
原理图如下所示:
writeBytes方法
void writeBytes(JNIEnv *env, jobject this, jbyteArray bytes,
jint off, jint len, jboolean append, jfieldID fid){
...
if (len == 0) {
return;
} else if (len > BUF_SIZE) {
buf = malloc(len); // 分配一片空间
...
}
...
// 将java堆内存空间中的bytes数组中的内容复制到buf中
(*env)->GetByteArrayRegion(env, bytes, off, len, (jbyte *)buf);
if (!(*env)->ExceptionOccurred(env)) {
off = 0;
while (len > 0) {
...
if (append == JNI_TRUE) {
...
} else {
n = IO_Write(fd, buf+off, len); // 将buf中的数据写入到OS中
}
...
}
}
...
}
关键流程如下:
- 分配一片空间
- 将java堆内存空间中的bytes数组中的内容复制到buf中
- 将buf中的数据写入到OS中
原理图如下所示: