由于在看io源码中偶尔看到FileDescriptor,所以本篇决定讲讲FileDescriptor,从字面上来看它就是文件描述符。
官方是这么描述的:
可以看出,FileDescriptor可以看做一种指向文件引用的抽象化概念。它能表示一个开放文件,一个开放的socket或者一个字节的源。它最主要的用途就是去创建FileInputStream或者FileOutputStream。并且也说了不应该创建应用自己的文件描述符。
不得不说每次看这种官方的解释都表示云里雾里,毕竟与那些大神们差距太大,只能细细品味了。
不知道还是否记得前面讲述的System.out,System.in,System.error了,当时它们初始化的时候就用到了FileDescriptor:
当时没有细说,本篇则来探索一下FileDescriptor。
下面先贴上源码,通过源码来进行一定程度地学习:
package java.io;
import java.util.ArrayList;
import java.util.List;
public final class FileDescriptor {
//封装了一个int型的值fd,每个打开的文件,socket等都会给你一个fd值,通过该值可以对文件,socket等进行相关操作,有点儿类似操作对象的索引
private int fd;
//
private Closeable parent;
//
private List otherParents;
//封装了一个boolean类型变量closed,用来判断fd是否被释放
private boolean closed;
//不带参构造,默认fd为-1,目测-1没什么意义,符合官方说的不建议直接创建自己的对象
public /**/ FileDescriptor() {
fd = -1;
}
//一个带参私有的构造方法,将fd值设为传入的参数
private /* */ FileDescriptor(int fd) {
this.fd = fd;
}
//定义的一个标准输入,fd值为0
public static final FileDescriptor in = new FileDescriptor(0);
//定义的一个标准输出,fd值为1
public static final FileDescriptor out = new FileDescriptor(1);
//定义的一个标准错误输出,fd值为2,我想在不同的环境下,标准流都能工作的原因应该就是因为已经在这里限定好了吧
public static final FileDescriptor err = new FileDescriptor(2);
//判断是否有效,这也证实了前面构造函数中fd赋值为-1是无效的
public boolean valid() {
return fd != -1;
}
public native void sync() throws SyncFailedException;
/* This routine initializes JNI field offsets for the class */
private static native void initIDs();
static {
initIDs();
}
// Set up JavaIOFileDescriptorAccess in SharedSecrets
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) {
throw new UnsupportedOperationException();
}
public long getHandle(FileDescriptor obj) {
throw new UnsupportedOperationException();
}
}
);
}
//像otherParents集合中添加元素
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);
}
}
//关闭otherParents所有的元素
@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 (ioe != null)
ex.addSuppressed(ioe);
ioe = ex;
} finally {
if (ioe != null)
throw ioe;
}
}
}
}
因为笔者也是一个初学者,看了源码后,也没有发现什么,只是知道了下其中定义了3个标准分别为in,out,err,java中的标准流就是通过这里来实现的。其次就是fd的值不是我们可以随意修改的,是底层给我们的,我们可以通过该值取进行操作。并且FileDescriptor中需要进行close方法执行,应该是对文件等操作结束后,执行完close以后,才真正释放该索引以及对应的内存读写空间。
下面用1个小例子来展示一下FileDescriptor的使用及特性:
package test;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test4 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
fos.write("FileDescriptor:这里证明了System.out,就是使用FileDescriptor.out来创建的\r".getBytes());
System.out.println("System.out:控制台输出");
FileOutputStream fos1 = new FileOutputStream("./src/file/test.txt");
FileOutputStream fos2 = new FileOutputStream("./src/file/test.txt");
System.out.println("fos1:"+fos1.getFD());
System.out.println("fos2:"+fos2.getFD());
fos.close();
fos1.close();
fos2.close();
}
}
执行上述代码可以在控制台看到如下打印:
第一句是一个使用了FileDescriptor.out创建的一个输出流打印出来,第二句为System.out打印出来的,两个流都是在控制台输出,再次证实了Sytem的in,out,err是通过FileDescriptor中的in,out,err来实现的。
然后我们看到对同一文件建立的两个不同的流,其fd值是不同的。
最后总结一下,这个类我们好像不怎么能使用到,因为我们无法去定义其中的fd值,所以我们能使用的基本只有其中为我们定义好的in,out,err了,但是众所周知,java已将帮我们封装好了更方便的System.in/out/err了,所以平常我们基本见不到其使用,只有在看源码时,会经常碰到了。
以上为本篇的内容。