关于 java.io.FileInputStream 和 java.io.FileOutputStream 的部分笔记,这两个类完成的是对文件的输入输出的字节流操作。本文演示代码段的执行环境基于JDK版本1.7。
概述
FileInputStream和FileOutputStream用来完成对文件的字节流操作。FileInputStream从文件系统的某个文件中获取数据并读取到内存中,FileOutputStream则将内存中的数据写到某个特定的文件中去。这两个类处理的是诸如图像数据这样的原始字节流,如果需要读取写入类似于字符这样的数据流,需要用FileReader和FileWriter。
继承关系
1 | // FileInputStream |
实现接口
类名 | 实现接口 |
---|---|
FileInputStream | Closeable, AutoCloseable |
FileOutputStream | Closeable, Flushable, AutoCloseable |
FileInputStream
主要字段
fd
1 | private final FileDescriptor fd; |
当前输入流中维护的文件描述符。用来标识进程中打开的一个文件。
channel
1 | private FileChannel channel = null; |
文件管道,用来读、写、映射、操作某个文件。
path
1 | private final String path; |
维护当前打开的文件路径。
构造方法
FileInputStream(String name)
1 | public FileInputStream(String name) throws FileNotFoundException { |
根据一个系统相关的字符串来获取一个文件并创建一个输入流,该输入流会从入参name文件中读取数据到内存中。实际调用的是下面的构造方法public FileInputStream(File file)。
FileInputStream(File file)
1 | public FileInputStream(File file) throws FileNotFoundException { |
根据一个入参file创建一个输入流,该输入流将从入参file中获取数据。第3 ~ 6行代码如果存在安全管理器,那么就需要检查是否拥有对该文件进行操作和管理的权限。第7 ~ 12行代码完成一些有效性校验。第13行维护了一个文件描述符,该描述符记录了当前打开的文件。第16行代码则通过调用底层的private native void open(String name)方法来打开特定路径下的文件。
FileInputStream(FileDescriptor fdObj)
1 | public FileInputStream(FileDescriptor fdObj) { |
通过一个文件描述符来初始化一个文件输入流。因为文件描述符fdObj记录了一个打开的文件,所以输入流中的字段fd与入参fdObj记录的是同样的文件。第2 ~ 8行代码如果存在安全管理器,那么就需要检查是否拥有对该文件进行操作和管理的权限。
部分方法
public int read()
1 | public int read() throws IOException { |
从path指定的文件中读取一个字节的内容并返回。第2行和第7行的代码用来完成I/O文件操作的一个跟踪记录,所以这两个方法是配对使用的,用来标记文件操作的开始和结束(具体使用场景和实现还有待考证)。底层调用的是private native int read0(),通过该方法完成从文件中读取数据的操作。
public int read(byte b[], int off, int len)
1 | public int read(byte b[], int off, int len) throws IOException { |
从path指定的文件中读取指定长度的内容到入参数组b中。可能因为已经读到了文件尾部、异常抛出等原因会导致实际返回的b的长度有可能小于入参len。底层调用的是private native int readBytes(byte b[], int off, int len),通过该方法完成从文件中读取数据的操作。
public void close()
1 | public void close() throws IOException { |
关闭当前输入流并释放所有相关的资源。如果在期间创建了文件管道,那么首先需要把该管道占用的资源释放,同时更新文件描述符的信息。在第15 ~ 17行代码中有两个条件来控制是否执行close方法释放输入流的资源。useCount <= 0表示当前文件描述符已经没有其他对象使用了,所以如果该条件满足的话就可以执行释放操作了。!isRunningFinalize()的值由方法finalize()负责维护。在finalize()方法中,首先向线程本地变量中传入一个true值,然后调用close()方法释放资源。此时,如果文件描述符fd还有其他流在使用,那么就不会执行close0()方法。在finalize()方法的最后将线程本地变量的值变成了false,那么当下次直接调用close方法的时候,不管fd是否还有其他流在使用,都会执行第16行代码,释放所有资源。
protected void finalize()
1 | protected void finalize() throws IOException { |
finalize()方法不会释放仍被其他流使用的文件描述符fd,所以第3行代码向线程本地变量中存入了一个true,这样在第5行代码调用close()方法时就不会释放当前资源。最后向线程本地变量中存入了false,所以那当下次直接调用close方法的时候,不管fd是否还有其他流在使用,都会释放所有资源。
public final FileDescriptor getFD()
1 | public final FileDescriptor getFD() throws IOException { |
获取并返回一个文件描述符的引用,否则抛出一个IOException给调用方。
public FileChannel getChannel()
1 | public FileChannel getChannel() { |
获取一个文件管道。该管道可以用来完成文件复制等一系列操作。每次创建文件管道时需要维护文件描述符的信息。
FileOutputStream
主要字段
fd
1 | private final FileDescriptor fd; |
当前输入流中维护的文件描述符。用来标识进程中打开的一个文件。
path
1 | private final String path; |
维护当前打开的文件路径。
append
1 | private final boolean append; |
写文件的模式,true 为从文件尾部追加写,false为从文件开头写并覆盖当前文件已有内容。
channel
1 | private FileChannel channel; |
文件管道,用来读、写、映射、操作某个文件。
构造方法
public FileOutputStream(String name)
1 | public FileOutputStream(String name) throws FileNotFoundException { |
根据一个系统相关的字符串来获取一个文件并创建一个输出流,该输出流会把数据写入到入参指定路径下的文件中。因为在写文件时有追加模式和覆盖模式可供选择,所以该方法默认写文件是覆盖当前已有内容从头开始写文件。实际调用的是下面的构造方法FileOutputStream(File file)。
public FileOutputStream(String name, boolean append)
1 | public FileOutputStream(String name, boolean append) |
根据一个系统相关的字符串来获取一个文件并创建一个输出流,该输出流会把数据写入到入参指定路径下的文件中。因为在写文件时有追加模式和覆盖模式可供选择,由入参append决定该方法采用哪种模式写文件。实际调用的是下面的构造方法FileOutputStream(File file, boolean append)。
public FileOutputStream(File file)
1 | public FileOutputStream(File file) throws FileNotFoundException { |
根据一个入参file创建一个输出流,该输出流会把数据写到入参file指定的文件中。该方法默认写文件是覆盖当前已有内容从头开始写文件。
public FileOutputStream(File file, boolean append)
1 | public FileOutputStream(File file, boolean append) |
根据一个入参file创建一个输出流,该输出流会把数据写到入参file指定的文件中。第5 ~ 8行代码如果存在安全管理器,那么就需要检查是否拥有对该文件进行操作和管理的权限。第9 ~ 14行代码完成一些有效性校验。第15行维护了一个文件描述符,该描述符记录了当前打开的文件。第19行代码则通过调用底层的private native void open(String name, boolean append)方法来打开特定路径下的文件。
public FileOutputStream(FileDescriptor fdObj)
1 | public FileOutputStream(FileDescriptor fdObj) { |
通过一个文件描述符来初始化一个文件输出流。因为文件描述符fdObj记录了一个打开的文件,所以输出流中的字段fd与入参fdObj记录的是同样的文件。第2 ~ 8行代码如果存在安全管理器,那么就需要检查是否拥有对该文件进行操作和管理的权限。
部分方法
public void write(int b)
1 | public void write(int b) throws IOException { |
向path指定的文件中以append指定的写入方式写入一个字节的内容。第2行和第8行的代码用来完成I/O文件操作的一个跟踪记录,所以这两个方法是配对使用的,用来标记文件操作的开始和结束(具体使用场景和实现还有待考证)。底层调用的是private native void write(int b, boolean append),通过该方法完成向文件中写入数据的操作。
public void write(byte b[], int off, int len)
1 | public void write(byte b[], int off, int len) throws IOException { |
向path指定的文件中以append指定的写入方式写入数组b中包含的内容。第2行和第8行的代码用来完成I/O文件操作的一个跟踪记录,所以这两个方法是配对使用的,用来标记文件操作的开始和结束(具体使用场景和实现还有待考证)。底层调用的是private native void write(byte b[], int off, int len, boolean append),通过该方法完成向文件中写入数据的操作。
public void close()
1 | public void close() throws IOException { |
执行逻辑同FileInputStream.close(),故不予赘述。
protected void finalize()
1 | protected void finalize() throws IOException { |
执行逻辑同FileInputStream.finalize(),故不予赘述。
public final FileDescriptor getFD()
1 | public final FileDescriptor getFD() throws IOException { |
获取并返回一个文件描述符的引用,否则抛出一个IOException给调用方。
public FileChannel getChannel()
1 | public FileChannel getChannel() { |
获取一个文件管道。该管道可以用来完成文件复制等一系列操作。每次创建文件管道时需要维护文件描述符的信息。
涉及基础知识点
FileDescriptor:
文件描述符,简称fd。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在Unix或者Linux系统中,文件描述符标识了内核中一个特定进程正在访问的文件。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,将fd作为参数传送给 read 或 write。在UNIX的传统实现中,文件描述符会被加入到一个内核维护的列表中,该列表中的每个元素指向了一个文件表(file table),这个文件表中维护了当前进程中所有的被打开的文件信息,包括一个指向文件的Inode对象的指针、相关元数据,如当前文件格式、读取模式(read,write,append,read-write……)等信息。file table中的每个元素会指向一个真实文件的物理存储地址。可参考如下图1所示:
图 - 1最左边为维护文件描述符的列表,其中0(标准输入),1(标准输出),2(标准异常输出)为标准的POSIX文件描述符。每个文件描述符会指向File table中的某个文件操作模式,多个文件描述符可以指向同一个文件操作模式。最右边维护了当前进程中打开的所有的真实文件信息,由file table中的某个元素指向其中的某个打开的文件来完成对应的文件操作。
总结下来,fd只是一个int型的数值,用来标定一个打开的文件,类似于索引的作用。在需要操作文件时,通过fd来指向其标定的那个文件。
参考文献
- Wikipedia. 文件描述符 [E]
- 木杉. JDK源码阅读-FileDescriptor [E]
- WIKIMEDIA COMMONS. File table and inode table [E]
- 鸡蛋卷啊卷. FileDescriptor文件描述符与Linux文件系统 [E]
- 平林新袖. FileDescriptor [E]
- mm_hh. Linux下 文件描述符(fd)与 文件指针(FILE*) [E]
- Axtaxt. IO trace generation in java: experimenting with sun.misc.IoTrace [E]