Java的readBytes是怎么实现的?

打印 上一主题 下一主题

主题 880|帖子 880|积分 2642

1.前言

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

从FileInputStream.java中看到readBytes最后是native调用
  1. /**
  2.      * Reads a subarray as a sequence of bytes.
  3.      * @param b the data to be written
  4.      * @param off the start offset in the data
  5.      * @param len the number of bytes that are written
  6.      * @exception IOException If an I/O error has occurred.
  7.      */
  8.     private native int readBytes(byte b[], int off, int len) throws IOException; // native调用
  9.     /**
  10.      * Reads up to b.length bytes of data from this input
  11.      * stream into an array of bytes. This method blocks until some input
  12.      * is available.
  13.      *
  14.      * @param      b   the buffer into which the data is read.
  15.      * @return     the total number of bytes read into the buffer, or
  16.      *             -1 if there is no more data because the end of
  17.      *             the file has been reached.
  18.      * @exception  IOException  if an I/O error occurs.
  19.      */
  20.     public int read(byte b[]) throws IOException {
  21.         return readBytes(b, 0, b.length);
  22.     }
复制代码
从jdk源码中,我们找到FileInputStream.c(/jdk/src/share/native/java/io),此文件定义了对应文件的native调用.
  1. // FileInputStream.c
  2. JNIEXPORT jint JNICALL
  3. Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
  4.         jbyteArray bytes, jint off, jint len) {
  5.     return readBytes(env, this, bytes, off, len, fis_fd);
  6. }
复制代码
我们观察下当前的目录,可以看到java 对典型的四种unix like的系统(bsd, linux, macosx, solaris), 以及windows 提供了特殊实现。share是公用部分。

在头部获取文件fd field (fd 是非负正整数,用来标识打开文件)
  1. // FileInputStream.c
  2. JNIEXPORT void JNICALL
  3. Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
  4.     fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;"); /* fd field,后面用来获取 fd */
  5. }
复制代码
 继续调用readBytes
  1. // ioutil.c
  2. jint
  3. readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
  4.           jint off, jint len, jfieldID fid)
  5. {
  6.     jint nread;
  7.     char stackBuf[BUF_SIZE];
  8.     char *buf = NULL;
  9.     FD fd;
  10.     if (IS_NULL(bytes)) {
  11.         JNU_ThrowNullPointerException(env, NULL);
  12.         return -1;
  13.     }
  14.     if (outOfBounds(env, off, len, bytes)) { /* 越界判断 */
  15.         JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
  16.         return -1;
  17.     }
  18.     if (len == 0) {
  19.         return 0;
  20.     } else if (len > BUF_SIZE) {
  21.         buf = malloc(len); /* 缓冲区不足,动态分配内存 */
  22.         if (buf == NULL) {
  23.             JNU_ThrowOutOfMemoryError(env, NULL);
  24.             return 0;
  25.         }
  26.     } else {
  27.         buf = stackBuf;
  28.     }
  29.     fd = GET_FD(this, fid); /* 获取fd */
  30.     if (fd == -1) {
  31.         JNU_ThrowIOException(env, "Stream Closed");
  32.         nread = -1;
  33.     } else {
  34.         nread = IO_Read(fd, buf, len); /* 执行read,系统调用 */
  35.         if (nread > 0) {
  36.             (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
  37.         } else if (nread == -1) {
  38.             JNU_ThrowIOExceptionWithLastError(env, "Read error");
  39.         } else { /* EOF */
  40.             nread = -1;
  41.         }
  42.     }
  43.     if (buf != stackBuf) {
  44.         free(buf); /* 失败释放内存 */
  45.     }
  46.     return nread;
  47. }
复制代码
我们继续看看IO_Read的实现,是个宏定义
#define IO_Read handleReadhandleRead有两种实现
solaris实现:
  1. // /jdk/src/solaris/native/java/io/io_util_md.c
  2. ssize_t
  3. handleRead(FD fd, void *buf, jint len)
  4. {
  5.     ssize_t result;
  6.     RESTARTABLE(read(fd, buf, len), result);
  7.     return result;
  8. }
  9. /*
  10. * Retry the operation if it is interrupted
  11. */
  12. #define RESTARTABLE(_cmd, _result) do { \
  13.     do { \
  14.         _result = _cmd; \
  15.     } while((_result == -1) && (errno == EINTR)); \ /* 如果是中断,则不断重试,避免进程调度等待*/
  16. } while(0)
复制代码
read方法可以参考unix man page
windows实现:
  1. // jdk/src/windows/native/java/io/io_util_md.c
  2. JNIEXPORT
  3. jint
  4. handleRead(FD fd, void *buf, jint len)
  5. {
  6.     DWORD read = 0;
  7.     BOOL result = 0;
  8.     HANDLE h = (HANDLE)fd;
  9.     if (h == INVALID_HANDLE_VALUE) {
  10.         return -1;
  11.     }
  12.     result = ReadFile(h,          /* File handle to read */
  13.                       buf,        /* address to put data */
  14.                       len,        /* number of bytes to read */
  15.                       &read,      /* number of bytes read */
  16.                       NULL);      /* no overlapped struct */
  17.     if (result == 0) {
  18.         int error = GetLastError();
  19.         if (error == ERROR_BROKEN_PIPE) {
  20.             return 0; /* EOF */
  21.         }
  22.         return -1;
  23.     }
  24.     return (jint)read;
  25. }
复制代码
3.java异常初探
  1. // jdk/src/share/native/common/jni_util.c
  2. /**
  3. * Throw a Java exception by name. Similar to SignalError.
  4. */
  5. JNIEXPORT void JNICALL
  6. JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
  7. {
  8.     jclass cls = (*env)->FindClass(env, name);
  9.     if (cls != 0) /* Otherwise an exception has already been thrown */
  10.         (*env)->ThrowNew(env, cls, msg); /* 调用JNI 接口*/
  11. }
  12. /* JNU_Throw common exceptions */
  13. JNIEXPORT void JNICALL
  14. JNU_ThrowNullPointerException(JNIEnv *env, const char *msg)
  15. {
  16.     JNU_ThrowByName(env, "java/lang/NullPointerException", msg);
  17. }
复制代码
最后是调用JNI:
  1. // hotspot/src/share/vm/prims/jni.h
  2. jint ThrowNew(jclass clazz, const char *msg) {
  3.         return functions->ThrowNew(this, clazz, msg);
  4.     }
  5. jint (JNICALL *ThrowNew)
  6.       (JNIEnv *env, jclass clazz, const char *msg);
复制代码
4.总结

很多高级语言,有着不同的编程范式,但是归根到底还是(c语言)系统调用,c语言能够在更低的层面做非常多的优化。如果我们了解了这些底层的系统调用,就能看到问题的本质。
本文没有对JNI 做深入分析,后续继续解析。
5.参考

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表