操纵系统利用文件描写符来指代一个打开的文件,对文件的读写操纵,都需要文件描写符作为参数。Java固然在设计上利用了抽象水平更高的流来作为文件操纵的模子,可是底层依然要利用文件描写符与操纵系统交互,而Java世界里文件描写符的对应类就是FileDescriptor。
Java文件操纵的三个类:FileIntputStream,FileOutputStream,RandomAccessFile,打开这些类的源码可以看到都有一个FileDescriptor成员变量。
注:本文利用的JDK版本为8。
FileDescriptor与文件描写符
操纵系统中的文件描写符本质上是一个非负整数,个中0,1,2牢靠为尺度输入,尺度输出,尺度错误输出,措施接下来打开的文件利用当前历程中最小的可用的文件描写标记码,好比3。
文件描写符自己就是一个整数,所以FileDescriptor的焦点职责就是生存这个数字:
public final class FileDescriptor {
private int fd;
}
可是文件描写符是无法在Java代码里配置的,因为FileDescriptor只有私有和无参的结构函数:
public FileDescriptor() {
fd = -1;
}
private FileDescriptor(int fd) {
this.fd = fd;
}
那Java是在何时会配置FileDescriptor的fd字段呢?这要团结FileIntputStream,FileOutputStream,RandomAccessFile的代码来看了。
我们以FileInputStream为例,首先,FileInputStream有一个FileDescriptor成员变量:
public class FileInputStream extends InputStream
{
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);
}
private native void open0(String name) throws FileNotFoundException;
可是上面的代码也没有对FileDescriptor#fd举办赋值,实际上Java层面无法对他赋值,真正的逻辑是在FileInputStream#open0这个native要领中,这就要下载JDK的源码来看了:
// /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;
}