吃透Netty一:普通IO

Posted 吃透Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了吃透Netty一:普通IO相关的知识,希望对你有一定的参考价值。

这里以文件输入输出流: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;
    

流程如下:

  1. read(byte b[]) 直接调用read(byte b[], int off, int len)函数
  2. 校验byte数组是否为空
  3. 校验读取范围是否正确
  4. 校验读取长度
  5. 调用read()函数读入一个字节
  6. 验证字节是否到达了文件的末尾
  7. 将该字节数据保存到b数组中
  8. 循环将文件的数据,逐字节的从磁盘中读入放入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]);
        
    

流程如下:

  1. write(byte b[])直接调用write(byte b[], int off, int len)函数
  2. 校验数组不能为空
  3. 校验写入范围是否合理
  4. 校验写入长度是否为0
  5. 循环将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流程如下:

  1. read(byte b[]) 方法直接调用read(byte b[], int off, int len)方法
  2. read(byte b[], int off, int len)方法直接调用readBytes(byte b[], int off, int len)方法
  3. 可以看到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流程如下:

  1. write(byte b[])方法直接调用write(byte b[], int off, int len)方法
  2. write(byte b[], int off, int len)方法直接调用writeBytes(byte b[], int off, int len, boolean append)方法
  3. 可以看到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);
        
        ...
    
    ...

关键流程如下:

  1. malloc分配一片空间
  2. 调用read函数读取数据
  3. 将数据保存放入堆内存的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中
            
            ...
        
    
    ...

关键流程如下:

  1. 分配一片空间
  2. 将java堆内存空间中的bytes数组中的内容复制到buf中
  3. 将buf中的数据写入到OS中

原理图如下所示:

以上是关于吃透Netty一:普通IO的主要内容,如果未能解决你的问题,请参考以下文章

吃透Netty一:普通IO

学透吃透Netty底层通讯原理

高性能底层怎么运作?一文帮你吃透Netty架构原理

没吃透Netty底层通讯原理,还能算的上Java老司机?

吃透Redis:网络框架篇-多路复用器

吃透Redis:网络框架篇-多路复用器