当前位置: 首页 > news >正文

FileDescriptor的源码和使用注意事项(windows操作系统,JDK8)

操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符指向存储设备的不透明标识符。Java虽然在设计上使用了抽象程度更高的流来作为文件操作的模型,但是底层依然要使用文件描述符与操作系统交互,而Java世界里文件描述符的对应类就是FileDescriptor。同时,Java规定了FileDescriptor只能由JDK的其它类来创建(比如FileInputStream、FileOutputStream、RandomAccessFile等),不能由应用程序自己创建。
操作系统中的文件描述符本质上是一个非负整数,其中0,1,2固定为标准输入,标准输出,标准错误输出,如下所示(POSIX标准):


Java程序接打开的文件使用当前进程可用的文件描述符就被保存在了FileDescriptor中的int fd变量,因此FileDescriptor的核心功能都是围绕着int fd变量来运行的

package java.io; import java.util.ArrayList; import java.util.List; public final class FileDescriptor { private int fd; private long handle; private Closeable parent; private List<Closeable> otherParents; private boolean closed; //FileDescriptor只有无参的构造函数,保证了fd不能被应用程序设置 public /**/ FileDescriptor() { fd = -1; handle = -1; } static { initIDs(); } static { sun.misc.SharedSecrets.setJavaIOFileDescriptorAccess( new sun.misc.JavaIOFileDescriptorAccess() { public void set(FileDescriptor obj, int fd) { obj.fd = fd; } public int get(FileDescriptor obj) { return obj.fd; } public void setHandle(FileDescriptor obj, long handle) { obj.handle = handle; } public long getHandle(FileDescriptor obj) { return obj.handle; } } ); } //POSIX标准中的标准输入,和System.class有关 public static final FileDescriptor in = standardStream(0); //POSIX标准中的标准输出,和System.class有关 public static final FileDescriptor out = standardStream(1); //POSIX标准中的标准错误输出,和System.class有关 public static final FileDescriptor err = standardStream(2); public boolean valid() { return ((handle != -1) || (fd != -1)); } public native void sync() throws SyncFailedException; private static native void initIDs(); private static native long set(int d); private static FileDescriptor standardStream(int fd) { FileDescriptor desc = new FileDescriptor(); desc.handle = set(fd); return desc; } synchronized void attach(Closeable c) { if (parent == null) { // first caller gets to do this parent = c; } else if (otherParents == null) { otherParents = new ArrayList<>(); otherParents.add(parent); otherParents.add(c); } else { otherParents.add(c); } } @SuppressWarnings("try") synchronized void closeAll(Closeable releaser) throws IOException { if (!closed) { closed = true; IOException ioe = null; try (Closeable c = releaser) { if (otherParents != null) { for (Closeable referent : otherParents) { try { referent.close(); } catch(IOException x) { if (ioe == null) { ioe = x; } else { ioe.addSuppressed(x); } } } } } catch(IOException ex) { /* * If releaser close() throws IOException * add other exceptions as suppressed. */ if (ioe != null) ex.addSuppressed(ioe); ioe = ex; } finally { if (ioe != null) throw ioe; } } } }
一、设置int fd变量的值

FileDescriptor.class 的构造函数将int fd的值设置为了-1,但是操作系统中的文件描述符本质上是一个非负整数,因此FileDescriptor.class中表示文件描述符的int fd变量是在FileInputStream.class、FileOutputStream.class、RandomAccessFile.class等这些使用FileDescriptor.class的类中来设置的,比如FileInputStream.class

public class FileInputStream extends InputStream { /* File Descriptor - handle to the open file */ private final FileDescriptor fd; //在FileInputStream实例化时,会新建FileDescriptor实例,并使用fd.attach(this)关联FileInputStream实例与FileDescriptor实例,这是为了之后在程序中关闭文件描述符做准备。 public FileInputStream(File file) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); ...省略代码... fd = new FileDescriptor(); fd.attach(this); path = name; open(name); } private void open(String name) throws FileNotFoundException { open0(name); } //真正对FileDescriptor.class中int fd赋值的逻辑是JNI调用的FileInputStream#open0这个native函数中 private native void open0(String name) throws FileNotFoundException; }
// /jdk/src/share/native/java/io/FileInputStream.c JNIEXPORT void JNICALL Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) { fileOpen(env, this, path, fis_fd, O_RDONLY); } // /jdk/src/solaris/native/java/io/io_util_md.c void fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags) { WITH_PLATFORM_STRING(env, path, ps) { FD fd; #if defined(__linux__) || defined(_ALLBSD_SOURCE) /* Remove trailing slashes, since the kernel won't */ char *p = (char *)ps + strlen(ps) - 1; while ((p > ps) && (*p == '/')) *p-- = '\0'; #endif fd = JVM_Open(ps, flags, 0666); // 打开文件拿到文件描述符 if (fd >= 0) { SET_FD(this, fd, fid); // 非负整数认为是正确的文件描述符,设置到fd变量 } else { throwFileNotFoundException(env, path); // 负数认为是不正确文件描述符,抛出FileNotFoundException异常 } } END_PLATFORM_STRING(env, ps); }

到了JDK的JNI代码中,使用JVM_Open打开文件,得到文件描述符,而JVM_Open已经不是JDK的方法了,而是JVM提供的方法,所以需要继续查看hotspot中的实现:

// /hotspot/src/share/vm/prims/jvm.cpp JVM_LEAF(jint, JVM_Open(const char *fname, jint flags, jint mode)) JVMWrapper2("JVM_Open (%s)", fname); //%note jvm_r6 int result = os::open(fname, flags, mode); // 调用os::open打开文件 if (result >= 0) { return result; } else { switch(errno) { case EEXIST: return JVM_EEXIST; default: return -1; } } JVM_END // /hotspot/src/os/linux/vm/os_linux.cpp int os::open(const char *path, int oflag, int mode) { if (strlen(path) > MAX_PATH - 1) { errno = ENAMETOOLONG; return -1; } int fd; int o_delete = (oflag & O_DELETE); oflag = oflag & ~O_DELETE; fd = ::open64(path, oflag, mode); // 调用open64打开文件 if (fd == -1) return -1; // 问打开成功也可能是目录,这里还需要判断是否打开的是普通文件 { struct stat64 buf64; int ret = ::fstat64(fd, &buf64); int st_mode = buf64.st_mode; if (ret != -1) { if ((st_mode & S_IFMT) == S_IFDIR) { errno = EISDIR; ::close(fd); return -1; } } else { ::close(fd); return -1; } } #ifdef FD_CLOEXEC { int flags = ::fcntl(fd, F_GETFD); if (flags != -1) ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } #endif if (o_delete != 0) { ::unlink(path); } return fd; }

可以看到JVM最后使用open64这个函数打开文件,网上对于open64这个资料还是很少的,我找到的是man page for open64 (all section 2) - Unix & Linux Commands,从中可以看出,open64是为了在32位环境打开大文件的系统调用,但是不是标准的一部分。(这一部分不是很确定,因为没有明确的资料)
这里的open()函数不是我们以前学C语言时打开文件用的fopen()函数,fopen是C标准库里的函数,而open()不是,open()是POSIX规范中的函数,是不带缓冲的I/O,不带缓冲的I/O相关的函数还有read(),write(),lseek(),close(),不带缓冲指的是这些函数都调用内核中的一个系统调用,而C标准库为了减少系统调用,使用了缓存来减少read,write的内存调用。(参考《UNIX环境高级编程》)
因此,我们知道了FileInputStream#open是使用open()系统调用来打开文件,得到文件句柄,现在我们的问题要回到这个文件句柄是如何最终设置到FileDescriptor#fd,我们来看/jdk/src/solaris/native/java/io/io_util_md.c:fileOpen的关键代码:

fd = handleOpen(ps, flags, 0666); if (fd != -1) { SET_FD(this, fd, fid); } else { throwFileNotFoundException(env, path); }

如果文件描述符fd正确,通过SET_FD这个红设置到fid对应的成员变量上,如下宏所示:

#define SET_FD(this, fd, fid) \ if ((*env)->GetObjectField(env, (this), (fid)) != NULL) \ (*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))

SET_FD宏比较简单,获取FileInputStream上的fid这个变量ID对应的变量,然后设置这个变量的IO_fd_fdID对应的变量(FileDescriptor#fd)为文件描述符。

这个fid和IO_fd_fdID的来历可以参照/jdk/src/share/native/java/io/FileInputStream.c文件的开头,可以看到这样的代码:

// jdk/src/share/native/java/io/FileInputStream.c jfieldID fis_fd; /* id for jobject 'fd' in java.io.FileInputStream */ /************************************************************** * static methods to store field ID's in initializers */ JNIEXPORT void JNICALL Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) { fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;"); }

Java_java_io_FileInputStream_initIDs对应JAVA中FileInputStream.class源码中的static块调用的initIDs函数:

public class FileInputStream extends InputStream { /* File Descriptor - handle to the open file */ private final FileDescriptor fd; static { initIDs(); } private static native void initIDs(); }

还有jdk/src/solaris/native/java/io/FileDescriptor_md.c开头:

// jdk/src/solaris/native/java/io/FileDescriptor_md.c /* field id for jint 'fd' in java.io.FileDescriptor */ jfieldID IO_fd_fdID; /************************************************************** * static methods to store field ID's in initializers */ JNIEXPORT void JNICALL Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) { IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I"); }
http://www.jsqmd.com/news/1101708/

相关文章:

  • 如何用ZR.Admin.NET在3个月内构建企业级管理系统:一个.NET开发者的真实经历
  • openEuler/llm_solution智能应用平台深度解析:智能调优、运维、研究三大核心功能
  • 百度网盘直链解析工具终极指南:告别限速,拥抱全速下载新时代
  • Ansible自动化部署静态网站:Ubuntu 20.04 + Nginx最佳实践
  • 3分钟获取阿里云盘Refresh Token的终极指南:扫码授权,安全便捷
  • 基于规范不变HHO方法求解磁薛定谔方程的数值实现与验证
  • 2026餐饮SAAS系统维护商推荐:凤梨收银系统服务商深度解析
  • Hermes Agent 发布桌面客户端,两大版本更新解读!
  • 用AI优化简历,让你的求职之路不再迷茫!(收藏版)
  • Windows上的安卓应用革命:APK安装器如何重新定义跨平台体验
  • JavaScript字符串底层原理与高性能实践
  • 新手搭建 OpenClaw 智能助手,规避 99% 安装失败的实用操作步骤(含安装包)
  • .NET Core微服务架构下的安全纵深防御实践:从认证到AI集成的全链路防护
  • 3步实现专业级虚拟背景:obs-backgroundremoval零配置指南
  • AI结对编程真能替代初级工程师?——基于12家上市公司真实人效数据的3个月AB测试报告
  • Deepfake换脸是什么?人脸核验系统怎么防?
  • Sora能生成60秒4K视频,可灵AI却主打“10秒精准可控”——视频生成新范式正在转移,你还在用旧标准评估吗?
  • AI 电动温奶器智能功率 MOSFET 完整选型方案
  • 如何通过运行时窗口编辑打破Windows应用程序的显示限制?
  • 从VS Code到JetBrains,AI插件配置全链路优化,手把手教你将代码生成准确率提升至89.6%
  • 从一张软著证书看三维电子沙盘的演进方向
  • AI编程工具正在偷走你的核心资产?2024年开发者必须知道的5条合规红线(GDPR/等保2.0/信创适配实操清单)
  • DDoS攻击肆虐频发,企业网络如何破局?德迅高防服务器筑牢安全屏障
  • Apifox 6 月更新|Apifox CLI 全面升级、导入导出优化、OAuth 2.0 支持自动刷新令牌
  • 告别杂乱背景:用AI背景移除插件打造专业直播间的秘密武器
  • 用OpenCode和大模型写代码半年,越用越爽!收藏这份程序员“换条路学”指南,小白也能轻松入门大模型时代!
  • 本地外卖平台怎么快速拉到商家入驻?我做共享配送系统三年,踩过的坑和验证过的方法全在这了
  • N皇后问题的遗传算法Python实战:从编码到收敛
  • 3分钟掌握原神账号完整数据的终极查询指南:如何用UID一键获取深度游戏分析
  • 视频节奏慢怎么快速变紧凑?5款剪气口软件深度对比