介绍
使用JAVA读取文件时需要用到FileInputStream这个类,最简单的使用方式如下:
public static void main(String[] args){
try {
FileInputStream fileInputStream = new FileInputStream("test.txt");
System.out.println(fileInputStream.read());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();FileInputStream源码中的构造方法一共有3个:public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
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 FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
if (security != null) {
security.checkRead(fdObj);
fd = fdObj;
path = null;
* FileDescriptor is being shared by streams.
* Register this stream with FileDescriptor tracker.
fd.attach(this);fd和path定义如下:private final FileDescriptor fd;
* The path of the referenced file
* (null if the stream is created with a file descriptor)
private final String path;参数为String name或者File file的构造方法都新建了一个fileDescriptor,并赋值给fd,而参数为FileDescriptor fdObj的构造方法直接将fdObj参数赋值给fd。其实从这里可以感觉出FileDescriptor(文件描述符)是JAVA中的文件操作核心。疑惑一
public static void main(String[] args) throws IOException, NoSuchFieldException {
FileDescriptor fileDescriptor = null;
FileDescriptor fileDescriptor1 = null;
FileInputStream fileInputStream = new FileInputStream("test.txt");
FileInputStream fileInputStream1 = new FileInputStream("test.txt");
System.out.println(fileInputStream.getFD().valid());
System.out.println(fileInputStream1.getFD().valid());
fileDescriptor = fileInputStream.getFD();
fileDescriptor1 = fileInputStream1.getFD();输出:其中fileDescriptor和fileDescriptor1的值分别为:
查看FileDescriptor的源码,2个构造方法如下:
public FileDescriptor() {
fd = -1;
private FileDescriptor(int fd) {
this.fd = fd;在FileDescriptor中的fd是一个int类型的值。FileDescriptor源码中只有一个public的构造方法,而且fd的初始值为-1,但是FileInputStream中的fd(FileDescriptor类型)的fd值通过调试看到不为-1(2个fd是包含的关系)。输出true的条件就是fd != -1。疑惑一解密
在参数为File file的构造方法的最后调用了一个open方法,初步怀疑在这个方法内改变了fd的内容。通过打断点调试,的确在open方法调用之后fd的值改变了。open方法的最终调用为:
private native void open0(String name) throws FileNotFoundException;可以看到是一个native方法,对应的JNI方法如下:JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) {
fileOpen(env, this, path, fis_fd, O_RDONLY);env是JNI的一个对象,this表示调用open方法的FileInputStream对象,path为传进来的参数(文件名),O_RDONLY表示只读,fis_fd是在JNI中定义的一个变量:jfieldID fis_fd;
* static methods to store field ID's in initializers
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;");fis_fd通过Java_java_io_FileInputStream_initIDs方法初始化,该方法对应了FileInputStream如下代码:所以,在FileInputStream类加载阶段,fis_fd就被初始化了,fid_fd相当于是fd字段的一个内存偏移量。open方法直接调用了fileOpen方法,fileOpen方法如下:
void
fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
WITH_PLATFORM_STRING(env, path, ps) {
FD fd;
char *p = (char *)ps + strlen(ps) - 1;
while ((p > ps) && (*p == '/'))
*p-- = '\0';
fd = handleOpen(ps, flags, 0666);
if (fd != -1) {
SET_FD(this, fd, fid);
} else {
throwFileNotFoundException(env, path);
} END_PLATFORM_STRING(env, ps);其中的handleOpen函数打开了一个文件句柄(一个数字),相当于和文件建立了联系,并且将返回的句柄赋值给了局部变量fd,然后调用了SET_FD宏: ((*env)->GetObjectField(env, (this), (fid)) != NULL) \
(*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))该函数首先判断FileInputStream这个对象的fd属性是不是空,如果不为空,则进行赋值。fd是刚得到的文件句柄,(*env)->GetObjectField(env, (this), (fid))是FileInputStream对象的fd字段。但是句柄fd是int类型的,而FileInputStream对象的fd字段是FileDescriptor类型的,如何赋值?理所当然,我们需要一个偏移量,一个FileDescriptor中的fd字段的偏移量,也就是IO_fd_fdID的值。IO_fd_fdID是在FileDescriptor对应JNI代码的一个变量,在类加载时期初始化,通过静态代码块:对应的native方法如下:
jfieldID IO_fd_fdID;
* static methods to store field ID's in initializers
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");由此可得,调用open方法之后,FileInputSream对象的fd的值被改变了。疑惑二
既然FileDescriptor是文件操作的核心,那么read方法调用又是怎么和它联系起来的?
疑惑二解密
FileInputStream中的read方法:
public int read() throws IOException {
return read0();
private native int read0() throws IOException;对应的native方法:JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {
return readSingle(env, this, fis_fd);readSingle()方法:jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
jint nread;
char ret;
FD fd = GET_FD(this, fid);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
nread = IO_Read(fd, &ret, 1);
if (nread == 0) {
return -1;
} else if (nread == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Read error");
return ret & 0xFF;虽然java代码中没有表现出对fd的使用,但是在native代码中的确使用了fd。总结
JAVA中的文件操作最终都是要通过FileDescriptor,在Unix/Linux中的文件描述符就是一个数字,对应了进程打开文件数组的下标,该数组的0,1,2号文件分别表示标准输入、标准输出,标准错误输出。这和JAVA中是一致的,FileDescriptor中的fd为0,1,2时也表示同样的意义。所以以下代码也可以用于输出'A':
FileOutputStream fileOutputStream = new FileOutputStream(FileDescriptor.out);
fileOutputStream.write('A');当我们通过文件名或者文件对象new一个FileInputStream的时候,做了以下步骤:如果FileInputStream类尚未加载,则执行initIDs方法,否则这一步直接跳过。
如果FileDescriptor类尚未加载,则执行initIDs方法,否则这一步也直接跳过。
new一个FileDescriptor对象赋给FileInputStream的fd属性。
打开一个文件句柄。
将文件句柄赋给FileDescriptor对象的fd字段。
注:本文JDK版本为1.8
如若觉得本文尚可,欢迎转载交流,转载请在正文明显处注明原文地址,谢谢!