Java FileDescriptor源码理解及三个标准流
简介
java中的File类是无法对文件进行操作的,只有通过FileInputStream和FileOutputStream才能对文件进行操作。
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
open(name, append);
}
两个流主要的构造函数都是通过File类来进行生成的。首先都是先进行安全判断看是否有对该文件访问的权限,然后就都是生成一个FileDescriptor类,该类就是文件描述符,Java虚拟机通过该类实现对文件的定位。
FileDescriptor类参数及构造
private int fd;
private long handle;
private Closeable parent;
private List<Closeable> otherParents;
private boolean closed;
static {
initIDs();
}
/* This routine initializes JNI field offsets for the class */
private static native void initIDs();
该类最重要的参数是fd和handle,其中fd是文件的描述符,文件描述符描述了文件的具体信息,handle是文件的句柄,不是文件资源是操作系统用来操作文件资源的。在类初始化过程中会调用initIDs()方法,而这个方法是native的需要打开jdk来进行查看。
openjdk\jdk\src\windows\native\java\io\FileDescriptor_md.c:
/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;
/* field id for jlong 'handle' in java.io.FileDescriptor */
jfieldID IO_handle_fdID;
/**************************************************************
* static methods to store field IDs in initializers
*/
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
IO_handle_fdID = (*env)->GetFieldID(env, fdClass, "handle", "J");
}
可以看出在源码中对initIDs的定义是获取fd和handle在class中的ID,这个ID用于获取在每个类中该变量的位置,对于每个native方法来说加载到内存中所有的变量是所有class共享的。所以每个class都初始化了handle和fd的id。而parent和otherParent是通过attach方法添加上的,用于管理所有的流。
initIDs()
在FileDescriptor中有一个initID()方法,该方法在FileDescriptor类被加载入的时候进行调用,该方法是native,所以他的实现在底层。
openjdk\jdk\src\windows\native\java\io\FileDescriptor_md.c
/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;
/* field id for jlong 'handle' in java.io.FileDescriptor */
jfieldID IO_handle_fdID;
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
IO_handle_fdID = (*env)->GetFieldID(env, fdClass, "handle", "J");
}
GetFieldID是用来获取响应class响应属性的id,通过id我们可以准确定位到每一个类中相应属性的位置。env表示的当前的环境,class表示我们需要查找的class,这两个参数在生成.h文件时候会自动导入,“fd”和“handle”表示属性的名称,后面一个参数表示属性类型,其中其表示的含义可以查看jdk文档。“I”表示的是int,“J”表示的是long。同时将文件中的IO_fd_fdID和IO_handle_fdID进行赋值,前一个值表示class中fd属性的id,后一个表示handle属性的id,通过id可以准确获取属性的位置,该文件下定义的属性对所有调用该文件的方法是共享的,所以当FileDescriptor被加载进内存后这两个值就不再改变了,防止每次都要重新获取浪费时间。
三个标准流
在Java中通过System来调用标准流
System.in // 标准输入流
System.out // 标准输出流
System.err // 标准错误流
可是打开源码发现System中的标准流全部为null
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
说明Java的输入输出流并没有定义在.java文件中,而是定义在了C语言的文件中,通过底层的参数直接获取输入输出流,在加载System类进入虚拟机时就自己初始化了。要看定义只能查看System初始化源码。
/**
* Initialize the system class. Called after thread initialization.
*/
private static void initializeSystemClass() {
...
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
...
}
我们在System的初始化static方法块中发现了三个初始化方法,该方法从注释中可以看出在线程初始化时调用该方法,作者能力有限没有找到源码哪里调用了该方法,我们就假定该方法在某个地方被调用了。在FileDescriptor中,对in,out和err有各自的定义,下面以in方法进行解析。
/**
* A handle to the standard input stream. Usually, this file
* descriptor is not used directly, but rather via the input stream
* known as {@code System.in}.
*
* @see java.lang.System#in
*/
public static final FileDescriptor in = standardStream(0);
private static FileDescriptor standardStream(int fd) {
FileDescriptor desc = new FileDescriptor();
desc.handle = set(fd);
return desc;
}
private static native long set(int d);
我们可以从代码中看出,在java中给in新定义了一个FileDescriptor类,同时调用set方法,设置fd为0,set方法是个native方法,所以其使用C++实现的。
openjdk\jdk\src\windows\native\java\io\FileDescriptor_md.c:
JNIEXPORT jlong JNICALL
Java_java_io_FileDescriptor_set(JNIEnv *env, jclass fdClass, jint fd) {
SET_HANDLE(fd);
}
openjdk\jdk\src\windows\native\java\io\io_util_md.h:
/*
* Setting the handle field in Java_java_io_FileDescriptor_set for
* standard handles stdIn, stdOut, stdErr
*/
#define SET_HANDLE(fd) \
if (fd == 0) { \
return (jlong)GetStdHandle(STD_INPUT_HANDLE); \
} else if (fd == 1) { \
return (jlong)GetStdHandle(STD_OUTPUT_HANDLE); \
} else if (fd == 2) { \
return (jlong)GetStdHandle(STD_ERROR_HANDLE); \
} else { \
return (jlong)-1; \
} \
GetStdHandle和STD_INPUT_HANDLE是由windows定义的api,返回了标准输入的句柄,然后将desc.handle赋值为标准输入的句柄,然后in指向了包含了标准输入句柄的FileDescriptor类。同理,标准输出,标准错误也是相同的方法,所以虽然System中的in、out、err为null但是可以被调用。
本文介绍了Java中FileDescriptor类的构造和初始化过程,包括initIDs()方法的底层实现,以及Java的三个标准流(输入、输出、错误)如何通过FileDescriptor与操作系统交互。虽然System类中标准流对象为null,但它们在底层通过C++实现,与操作系统API紧密关联。
350

被折叠的 条评论
为什么被折叠?



