• JNI探秘-----FileInputStream的read方法详解


                  作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可。

                 上一章我们已经分析过FileInputStream的构建过程,接下来我们就来看一下read方法的读取过程。

                 我们先来看下FileInputStream中的四个有关read的方法的源码,如下。

        public native int read() throws IOException;
    
        private native int readBytes(byte b[], int off, int len) throws IOException;
    
        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);
        }

                 可以看到,其中有两个本地方法,两个不是本地方法,但是还是内部还是调用的本地方法,那么我们研究的重点就是这两个本地方法到底是如何实现的。

                 下面是这两个本地方法的源码,非常简单,各位请看。

    JNIEXPORT jint JNICALL
    Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {
        return readSingle(env, this, fis_fd);//每一个本地的实例方法默认的两个参数,JNI环境与对象的实例
    }
    
    JNIEXPORT jint JNICALL
    Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
            jbyteArray bytes, jint off, jint len) {//除了前两个参数,后三个就是readBytes方法传递进来的,字节数组、起始位置、长度三个参数
        return readBytes(env, this, bytes, off, len, fis_fd);
    }

                 可以看到,这两个本地方法的实现只是将任务又转给了两个方法,readSingle和ReadBytes,请注意,在调用这两个方法时,除了常用的env和this对象,以及从JAVA环境传过来的参数之外,还多了一个参数fis_fd,这个对象就是上一章中FileInputStream类中的fd属性的地址偏移量了。

                 那么下面我们首先来看readSingle方法的实现,如下。

    /*
        env和this参数就不再解释了
        fid就是FileInputStream类中fd属性的地址偏移量
        通过fid和this实例可以获取FileInputStream类中fd属性的内存地址
    */
    jint
    readSingle(JNIEnv *env, jobject this, jfieldID fid) {
        jint nread;//存储读取后返回的结果值
        char ret;//存储读取出来的字符
        FD fd = GET_FD(this, fid);//这个获取到的FD其实就是之前handle属性的值,也就是文件的句柄
        if (fd == -1) {
            JNU_ThrowIOException(env, "Stream Closed");
            return -1;//如果文件句柄等于-1,说明文件流已关闭
        }
        nread = (jint)IO_Read(fd, &ret, 1);//读取一个字符,并且赋给ret变量
        //以下根据返回的int值判断读取的结果
        if (nread == 0) { /* EOF */
            return -1;//代表流已到末尾,返回-1
        } else if (nread == JVM_IO_ERR) { /* error */
            JNU_ThrowIOExceptionWithLastError(env, "Read error");//IO错误
        } else if (nread == JVM_IO_INTR) {
            JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);//被打断
        }
        return ret & 0xFF;//与0xFF做按位的与运算,去除高于8位bit的位
    }

             可以看到,这个方法其实最关键的就是IO_Read这个宏定义的处理,而IO_Read其实只是代表了一个方法名称叫handleRead,我们去看一下handleRead的源码。

    /*
        fd就是handle属性的值
        buf是收取读取内容的数组
        len是读取的长度,可以看到,这个参数传进来的是1
        函数返回的值代表的是实际读取的字符长度
    */
    JNIEXPORT
    size_t
    handleRead(jlong fd, void *buf, jint len)
    {
        DWORD read = 0;
        BOOL result = 0;
        HANDLE h = (HANDLE)fd;
        if (h == INVALID_HANDLE_VALUE) {//如果句柄是无效的,则返回-1
            return -1;
        }
        //这里ReadFile又是一个现有的函数,和上一章的CreateFile是一样的
        //都是WIN API的函数,可以百度搜索它的作用与参数详解,理解它并不难
        result = ReadFile(h,          /* File handle to read */  //文件句柄
                          buf,        /* address to put data */  //存放数据的地址
                          len,        /* number of bytes to read */  //要读取的长度
                          &read,      /* number of bytes read */  //实际读取的长度
                          NULL);      /* no overlapped struct */  //只有对文件进行重叠操作时才需要传值
        if (result == 0) {//如果没读取出来东西,则判断是到了文件末尾返回0,还是报错了返回-1
            int error = GetLastError();
            if (error == ERROR_BROKEN_PIPE) {
                return 0; /* EOF */
            }
            return -1;
        }
        return read;
    }

             到此,基本上就完全看完了无参数的read方法的源码,它的原理其实很简单,就是利用handle这个句柄,使用ReadFile的WIN API函数读取了一个字符,不过值得注意的是,这些都是windows系统下的实现方式,所以不可认为这些源码代表了所有系统下的情况。

             然而对于带有参数的read方法,其原理与无参read方法是一样的,而且最终也是调用的handleRead这个方法,只是读取的长度不再是1而已。

             由此可以看出,文件输入流只是已只读方式打开了一个文件流,而且这个文件流只能依次向后读取,因为在之前的设计模式系列装饰器模式一文中,LZ已经提到过,对于FileInputStream进行包装而支持回退,标记重置等操作的输入流,都只是在内存里创建缓冲区造成的假象,我们真正的文件输入流是不支持这些操作的。

             好了,有关FileInputstream的源码内容就分享到此了,如果有兴趣的猿友,可以继续看一下其它的本地方法是如何实现的。

             感谢各位的收看。



  • 相关阅读:
    python-MongoDB 非关系型数据库
    python-爬虫-Beautifulsoup模块
    python-flask-路由匹配源码分析
    python-flask-配置文件的源码分析
    python-爬虫-selenium模块
    python-flask-请求源码流程
    python-爬虫
    iOS开发——创建你自己的Framework
    手把手教你封装下载管理器
    AFNetwork 作用和用法详解
  • 原文地址:https://www.cnblogs.com/zuoxiaolong/p/jni2.html
Copyright © 2020-2023  润新知