Java FileDescriptor源码理解和三个标准流

本文介绍了Java中FileDescriptor类的构造和初始化过程,包括initIDs()方法的底层实现,以及Java的三个标准流(输入、输出、错误)如何通过FileDescriptor与操作系统交互。虽然System类中标准流对象为null,但它们在底层通过C++实现,与操作系统API紧密关联。

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但是可以被调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值