• Java的readBytes是怎么实现的?


    1.前言

    众所周知,Java是一门跨平台语言,针对不同的操作系统有不同的实现。本文从一个非常简单的api调用来看看Java具体是怎么做的.

    2.源码分析

    从FileInputStream.java中看到readBytes最后是native调用

    /**
         * Reads a subarray as a sequence of bytes.
         * @param b the data to be written
         * @param off the start offset in the data
         * @param len the number of bytes that are written
         * @exception IOException If an I/O error has occurred.
         */
        private native int readBytes(byte b[], int off, int len) throws IOException; // native调用
    
        /**
         * Reads up to b.length bytes of data from this input
         * stream into an array of bytes. This method blocks until some input
         * is available.
         *
         * @param      b   the buffer into which the data is read.
         * @return     the total number of bytes read into the buffer, or
         *             -1 if there is no more data because the end of
         *             the file has been reached.
         * @exception  IOException  if an I/O error occurs.
         */
        public int read(byte b[]) throws IOException {
            return readBytes(b, 0, b.length);
        }

    从jdk源码中,我们找到FileInputStream.c(/jdk/src/share/native/java/io),此文件定义了对应文件的native调用.

    // FileInputStream.c
    
    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);
    }

    我们观察下当前的目录,可以看到java 对典型的四种unix like的系统(bsd, linux, macosx, solaris), 以及windows 提供了特殊实现。share是公用部分。

    在头部获取文件fd field (fd 是非负正整数,用来标识打开文件)

    // FileInputStream.c
    
    JNIEXPORT void JNICALL
    Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
        fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;"); /* fd field,后面用来获取 fd */
    }

     继续调用readBytes

    // ioutil.c
    
    jint
    readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
              jint off, jint len, jfieldID fid)
    {
        jint nread;
        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 {
            buf = stackBuf;
        }
    
        fd = GET_FD(this, fid); /* 获取fd */
        if (fd == -1) {
            JNU_ThrowIOException(env, "Stream Closed");
            nread = -1;
        } else {
            nread = IO_Read(fd, buf, len); /* 执行read,系统调用 */
            if (nread > 0) {
                (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
            } else if (nread == -1) {
                JNU_ThrowIOExceptionWithLastError(env, "Read error");
            } else { /* EOF */
                nread = -1;
            }
        }
    
        if (buf != stackBuf) {
            free(buf); /* 失败释放内存 */
        }
        return nread;
    }

    我们继续看看IO_Read的实现,是个宏定义

    #define IO_Read handleRead

    handleRead有两种实现

    solaris实现:

    // /jdk/src/solaris/native/java/io/io_util_md.c
    
    ssize_t
    handleRead(FD fd, void *buf, jint len)
    {
        ssize_t result;
        RESTARTABLE(read(fd, buf, len), result);
        return result;
    }
    
    /*
     * Retry the operation if it is interrupted
     */
    #define RESTARTABLE(_cmd, _result) do { \
        do { \
            _result = _cmd; \
        } while((_result == -1) && (errno == EINTR)); \ /* 如果是中断,则不断重试,避免进程调度等待*/
    } while(0)

    read方法可以参考unix man page

    windows实现:

    // jdk/src/windows/native/java/io/io_util_md.c
    
    JNIEXPORT
    jint
    handleRead(FD fd, void *buf, jint len)
    {
        DWORD read = 0;
        BOOL result = 0;
        HANDLE h = (HANDLE)fd;
        if (h == INVALID_HANDLE_VALUE) {
            return -1;
        }
        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) {
            int error = GetLastError();
            if (error == ERROR_BROKEN_PIPE) {
                return 0; /* EOF */
            }
            return -1;
        }
        return (jint)read;
    }

    3.java异常初探

    // jdk/src/share/native/common/jni_util.c
    
    /**
     * Throw a Java exception by name. Similar to SignalError.
     */
    JNIEXPORT void JNICALL
    JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
    {
        jclass cls = (*env)->FindClass(env, name);
    
        if (cls != 0) /* Otherwise an exception has already been thrown */
            (*env)->ThrowNew(env, cls, msg); /* 调用JNI 接口*/
    }
    
    /* JNU_Throw common exceptions */
    
    JNIEXPORT void JNICALL
    JNU_ThrowNullPointerException(JNIEnv *env, const char *msg)
    {
        JNU_ThrowByName(env, "java/lang/NullPointerException", msg);
    }

    最后是调用JNI:

    // hotspot/src/share/vm/prims/jni.h
    
    jint ThrowNew(jclass clazz, const char *msg) {
            return functions->ThrowNew(this, clazz, msg);
        }
    
    jint (JNICALL *ThrowNew)
          (JNIEnv *env, jclass clazz, const char *msg);

    4.总结

    很多高级语言,有着不同的编程范式,但是归根到底还是(c语言)系统调用,c语言能够在更低的层面做非常多的优化。如果我们了解了这些底层的系统调用,就能看到问题的本质。

    本文没有对JNI 做深入分析,后续继续解析。

    5.参考

    https://man7.org/linux/man-pages/man2/read.2.html

  • 相关阅读:
    NAACL22 | 引入多模态对比学习来增强句子特征学习
    查询利器—索引
    【C++ STL】模拟实现 map 和 set(对一颗红黑树进行封装)
    android 不同进程之间数据传递
    ​Word处理控件Aspose.Words功能演示:在 Python 中将 Word 文档转换为 EPUB​
    深度学习 Day 16——利用卷神经网络实现咖啡豆的识别
    k8s-hpa控制器 16
    四六级听力考试高频词汇分类记忆-旅游交通类
    Qt 学习(三) —— Qt 模块
    红黑树刷题(上)
  • 原文地址:https://www.cnblogs.com/darcy-yuan/p/17591027.html