(参考使用 Java Native Interface 的最佳实践)
标准输入,标准输出,标准错误输出
标准输入,标准输出,标准错误输出是所有操作系统都支持的,对于一个进程来说,文件描述符0,1,2固定是标准输入,标准输出,标准错误输出。
Java对标准输入,标准输出,标准错误输出的支持也是通过FileDescriptor实现的,FileDescriptor
中定义了in,out,err这三个静态变量:
public static final FileDescriptor in = new FileDescriptor(0);
public static final FileDescriptor out = new FileDescriptor(1);
public static final FileDescriptor err = new FileDescriptor(2);
我们常用的System.out
等,就是基于这三个封装的:
public final class System {
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
/**
* 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")));
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
}
System作为一个特殊的类,类构造时无法实例化in/out/err
,构造发生在initializeSystemClass
被调用时,但是in/out/err
是被声明为final的,如果声明时和类构造时没有赋值,是会报错的,所以System在实现时,先设置为null,然后通过native方法来在运行时修改(学到了不少奇技淫巧。。),通过setIn0/setOut0/setErr0
的注释也可以说明这一点:
/*
* The following three functions implement setter methods for
* java.lang.System.{in, out, err}. They are natively implemented
* because they violate the semantics of the language (i.e. set final
* variable).
*/
JNIEXPORT void JNICALL
Java_java_lang_System_setIn0(JNIEnv *env, jclass cla, jobject stream)
{
jfieldID fid =
(*env)->GetStaticFieldID(env,cla,"in","Ljava/io/InputStream;");
if (fid == 0)
return;
(*env)->SetStaticObjectField(env,cla,fid,stream);
}
JNIEXPORT void JNICALL
Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream)
{
jfieldID fid =
(*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;");
if (fid == 0)
return;
(*env)->SetStaticObjectField(env,cla,fid,stream);
}
JNIEXPORT void JNICALL
Java_java_lang_System_setErr0(JNIEnv *env, jclass cla, jobject stream)
{
jfieldID fid =
(*env)->GetStaticFieldID(env,cla,"err","Ljava/io/PrintStream;");
if (fid == 0)
return;
(*env)->SetStaticObjectField(env,cla,fid,stream);
}
FileDescriptor关闭逻辑
FileDescriptor
的代码不多,除了上面提到的fd
成员变量,initIDs
初始化构造方法,in/out/err
三个标准描述符,只剩下attach
和closeAll
这两个方法,这两个方法和文件描述符的关闭有关。
上文提到过,FileInputStream
在实例化时,会新建FileDescriptor
并调用FileDescriptor#attach
方法绑定文件流与文件描述符。
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
FileDescriptor#attach
实现如下:
synchronized void attach(Closeable c) {
if (parent == null) {
// first caller gets to do this
parent = c;
} else if (otherParents == null) {
otherParents = new ArrayList<>();
otherP