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 <code>b.length</code> 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
* <code>-1</code> 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
Java的readBytes是怎么实现的?的更多相关文章
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- Java线上应用故障排查之一:高CPU占用
一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题. 根据top命令,发现PID为28555的Java进程占 ...
- java netty socket库和自定义C#socket库利用protobuf进行通信完整实例
之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...
- 我看不下去鸟。。。。Java和C#的socket通信真的简单吗?
这几天在博客园上看到好几个写Java和C#的socket通信的帖子.但是都为指出其中关键点. C# socket通信组件有很多,在vs 使用nuget搜索socket组件有很多类似的.本人使用的是自己 ...
- java.net.SocketException: Connection reset
java.net.SocketException: Connection reset at java.net.SocketInputStream.read(SocketInputStream.java ...
- Java Netty 4.x 用户指南
问题 今天,我们使用通用的应用程序或者类库来实现互相通讯,比如,我们经常使用一个 HTTP 客户端库来从 web 服务器上获取信息,或者通过 web 服务来执行一个远程的调用. 然而,有时候一个通用的 ...
- Cocos2d-JS/Ajax用Protobuf与NodeJS/Java通信
原文地址:http://www.iclojure.com/blog/articles/2016/04/29/cocos2d-js-ajax-protobuf-nodejs-java Google的Pr ...
- ASP.NET、JAVA跨服务器远程上传文件(图片)的相关解决方案整合
一.图片提交例: A端--提交图片 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string u ...
- java中 用telnet 判断服务器远程端口是否开启
package net.jweb.common.util; import java.io.BufferedReader; import java.io.BufferedWriter; import j ...
- Java 把 InputStream 转换成 String 的几种方法
我们在 Java 中经常会碰到如何把 InputStream 转换成 String 的情形,比如从文件或网络得到一个 InputStream,需要转换成字符串输出或赋给别的变量. 未真正关注这个问题之 ...
随机推荐
- cf1774f解题报告
Magician and Pigs 分析一下三个操作分别干了些什么 新添一只猪 使血量为 \(x\) 的猪血量变为 \(\max(x-v,0)\) 设前面操作后猪总共会受到 \(s\) 的伤害,复制一 ...
- ArcGIS Pro创建、发布、调用GP服务全过程示例(等高线分析)
在之前的文章介绍过使用ArcMap发布GP分析服务,由于ArcGIS后续不在更新ArcMap,改用ArcGIS Pro,本文对ArcGIS Pro发布GP分析服务进行说明. 本文以等高线分析为例,使用 ...
- 在vue标签代码块中定义变量
方式一: 在标签上使用:set关键字,不管什么标签都可以 <template> <h1>test</h1> <template :set="firs ...
- js验证统一社会信用代码
js验证统一社会信用代码 //验证信用代码 function CheckSocialCreditCode(Code) { var patrn = /^[0-9A-Z]+$/; //18位校验及大写校验 ...
- Vue2到Vue3的改变
一.Vue2->Vue3 如果有Vue2的基础,并在此基础上学习Vue3,并不需要把完整的官网看完,我们只需要关注一下新功能和非兼容的变化即可进行开发. 二.Vue3变化 统一元素上使用的v-i ...
- 2022-05-12:小歪每次会给你两个字符串: 笔记s1和关键词s2,请你写一个函数, 判断s2的排列之一是否是s1的子串。 如果是,返回true; 否则,返回false。 来自字节飞书团队。
2022-05-12:小歪每次会给你两个字符串: 笔记s1和关键词s2,请你写一个函数, 判断s2的排列之一是否是s1的子串. 如果是,返回true: 否则,返回false. 来自字节飞书团队. 答案 ...
- 2021-05-05:一个数组中只有两种字符‘G‘和‘B‘,可以让所有的G都放在左侧,所有的B都放在右侧。或者可以让所有的G都放在右侧,所有的B都放在左侧。但是只能在相邻字符之间进行交换操作。返回至少
2021-05-05:一个数组中只有两种字符'G'和'B',可以让所有的G都放在左侧,所有的B都放在右侧.或者可以让所有的G都放在右侧,所有的B都放在左侧.但是只能在相邻字符之间进行交换操作.返回至少 ...
- 2021-06-21:贩卖机只支持硬币支付,且收退都只支持10 ,50,100三种面额。一次购买只能出一瓶可乐,且投钱和找零都遵循优先使用大钱的原则,需要购买的可乐数量是m, 其中手头拥有的10、50
2021-06-21:贩卖机只支持硬币支付,且收退都只支持10 ,50,100三种面额.一次购买只能出一瓶可乐,且投钱和找零都遵循优先使用大钱的原则,需要购买的可乐数量是m, 其中手头拥有的10.50 ...
- Ubuntu 18.04 (Bionic) 简单快速的安装mongodb
按步骤走,不带脑子式安装(注意4.0版本mongodb官方已经不再支持,以下代码中可以修改mongodb版本号安装,目前最新版为6.0,如果懒得改直接用也可以,文章后边第三章第一条代码会直接升级为最新 ...
- vscode 配置代码自动格式化加修复
子曰:"工欲善其事,必先利其器", 编码必须的就是有一个顺手的ide,然而光有还不行,还要懂得配置,毕竟不同的团队代码规范不同,如目前用得较多的就是eslint,今天就顺便记录下v ...