本篇要学习的是java io包中的FileInputStream类和FileOoutputStream类。
文件是我们常见的数据源之一,所以java为我们封装好了支持文件读写的流工具,下面我们通过源码分别来学习这两个类。
FileInputStream.java:
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public class FileInputStream extends InputStream
{
//内部声明了一个FileDescriptor对象的句柄,用于接收被打的文件的句柄。
private final FileDescriptor fd;
//内部声明了一个String类型的变量path,用于存放被打开文件的文件路径。
private final String path;
//声明了一个FileChannel对象,初始化为null。该对象后面在NIO的学习中会详细的提到,可以对文件进行操作
private FileChannel channel = null;
//定义了一个Object对象,为后面进行锁操作时提供对象锁。
private final Object closeLock = new Object();
//定义了一个booelan型变量,closed,来判断流是否关闭,注意的是该变量被volatile关键字修饰,保证了数据改变时的可见性。
private volatile boolean closed = false;
/**
* 一个带一个参数的构造函数,传入的参数为所要打开文件的文件路径。创建时进行安全监测,检测传入参数是否为null,然后调用下一个构造函数。
*/
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
/**
* 一个带一个参数的构造函数,传入的参数为所要打开文件的file对象。创建是进行安全监测,检测file对象是否为null。
*/
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
//获取当前应用的安全管理器SecruityManager,并对其是否有读取权限进行检测。
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
//如果传入文件的路径为null,抛出NullPointerException。
if (name == null) {
throw new NullPointerException();
}
//判断文件是否合法
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
//创建一个文件描述符,并将本类对象依附到该文件描述符上,并将全局变量path赋值为传入文件的路径,最终通过一个native的open方法打开文件。这里的attach
//方法目的时方便最后的close方法调用时,可以方便的关闭所有需要关闭的资源,这就是我们使用多个包装流的时候无需一个一个的关闭所有流的原因。
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
/**
* 一个带有一个参数的构造方法,传入的参数为一个文件描述符。开始时需要对传入参数进行安全监测,如果为null,则抛出NullPointerException。
*/
public FileInputStream(FileDescriptor fdObj) {
//获得java的安全管理器。
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
//如果成功获得了安全管理器,则对传入的文件描述符进行权限检测,是否具有读数据的权限。
if (security != null) {
security.checkRead(fdObj);
}
//将传入的文件描述符fdObj赋值给全局变量fd,全局变量path赋值为null,将本类对象依附到文件描述符之上。
fd = fdObj;
path = null;
fd.attach(this);
}
/**
* 一个native关键字修饰的open方法,含有一个String类型的参数,传入的参数为所需打开文件的路径。关于native方法因为牵扯到c/c++,以后再开篇幅具体描述。
* 此时只需知道该方法用于打开一个文件。
*/
private native void open(String name) throws FileNotFoundException;
/**
* read方法,实际调用native方法read0()来实际读取文件。
*/
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
/**
* 一个带参的read方法,传入的参数为一个byte型数组,最终调用native方法readBytes方法
*/
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
/**
* 一个native关键字修饰的skip方法,传入的参数为一个long型数据,方法用于跳过传入的长度进行阅读。
*/
public native long skip(long n) throws IOException;
/**
* 该方法返回一个预估的流中还可以读取的数据长度
*/
public native int available() throws IOException;
/**
* 用于关闭流。
*/
public void close() throws IOException {
//加了一个同步锁,用来设置closed状态值。
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
//如果文件管道不为null,调用相应的close方法。
if (channel != null) {
channel.close();
}
//调用文件描述符的closeAll方法,会将所有依附的系统资源全部释放。
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
/**
* 获得当前流中读取文件的文件描述符
*/
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
/**
* 获取当前流中所要读取文件的文件管道。
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, false, this);
}
return channel;
}
}
// 用于设置fd的内存地址偏移量,牵扯到JNI编程,这里不细说。
private static native void initIDs();
private native void close0() throws IOException;
static {
initIDs();
}
/**
* 重写了finalize方法,用于再一次确定资源的关闭。需要注意的是当fd被分享出去后,必须等到所有使用到fd引用的资源都不可达时,调用close方法。
*/
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
close();
}
}
}
FileOutput.java:
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public class FileOutputStream extends OutputStream
{
/**
* 内置了一个文件描述符的句柄,用于接收打开文件的文件描述符对象。
*/
private final FileDescriptor fd;
/**
* 声明了一个boolean类型的变量append,该变量值用于控制打开文件写入时是以追加的形式写入数据还是以覆盖的形式写入数据。
*/
private final boolean append;
/**
* 内置了一个文件管道,用于接收打开文件的文件管道对象。它的初始化不是最初就初始化的,只有在需要时(调用getChannel方法时)才会进行初始化。
*/
private FileChannel channel;
/**
* 声明了一个String类型的变量,用于接收打开文件的路径名。
*/
private final String path;
//定义了一个Object对象,为后面的同步操作提供同步锁。
private final Object closeLock = new Object();
//定义了一个boolean型变量closed,用来控制当前流对象是否关闭。用volatile关键修饰后,保证了数据改变时的可见性。
private volatile boolean closed = false;
/**
* 一个带有一个参数的构造函数,传入的参数类型为String类型,为要写入文件的路径,内部调用了下面的带两个参数的构造方法,默认写人模式是覆盖的。
*/
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
/**
* 一个带有两个参数的构造函数,第一个参数为String类型,为要写入文件的路径,第二个参数为一个boolean型变量,用于控制文件写入时的方式。内部调用另一个构造
* 函数。
*/
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
/**
* 一个带有一个参数的构造函数,传入的参数类型为一个File对象,内部继续调用其它的构造函数,默认写入方式是覆盖的。
*/
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
/**
* 带两个参数的构造函数,其它的构造函数最终都是调用该构造函数,第一个参数为一个File类型,表示要打开的文件,第二个参数是一个boolean型变量,用于控制写
* 入时是覆盖还是追加。
*/
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
//获取打开文件的路径。
String name = (file != null ? file.getPath() : null);
//获取java安全管理器,对当前是否具有写入的权限进行检查。
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
//检测是否成功获得文件路径名。
if (name == null) {
throw new NullPointerException();
}
//检测传入的文件是否合法。
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
//将该流与文件描述符相依附,为后面进行close时提供便捷。
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
//调用native方法open,打开需要就行写入的文件。
open(name, append);
}
/**
* 一个带一个参数的构造函数,参数类型是文件描述符类型,具体过程同上,此处不再细说。
*/
public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.append = false;
this.path = null;
fd.attach(this);
}
/**
* 用于打开指定文件,第一个String类型参数为需要打开文件的路径,第二个boolean类型参数为打开模式,是追加还是覆盖。
*/
private native void open(String name, boolean append)
throws FileNotFoundException;
/**
* 用于向打开文件中写入数据,第一个参数是一个int型变量,为要写入的数据,第二个boolean型参数为写入时是追加还是覆盖。
*/
private native void write(int b, boolean append) throws IOException;
/**
* 向文件中写入数据,内部调用上面的native方法进行写入。
*/
public void write(int b) throws IOException {
write(b, append);
}
/**
* 向文件中写入数据,不过不是一次写入一个字节,而是通过一个byte数组作为缓存,一次写入一批数据。
*/
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
/**
* 向文件中写入数据,将传入的byte数组中的内容全部写入至文件中。
*/
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
}
/**
* 向文件中写入数据,传入的三个参数分别是写入的数据源一个byte数组,后两个参数为写入数据的起点以及写入数据的长度。
*/
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
}
/**
* 该方法用于关闭流及与其相关联的所有的系统资源。
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
/**
* 获得打开文件的文件描述符。
*/
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
/**
* 获得打开文件的文件管道。
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, false, true, append, this);
}
return channel;
}
}
/**
* 重写了finalize方法,调用close方法保证流与其关联的资源能够获得释放。如果是标准输出流或者错误输出流,还会调用flush方法。
*/
protected void finalize() throws IOException {
if (fd != null) {
if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
flush();
} else {
close();
}
}
}
private native void close0() throws IOException;
private static native void initIDs();
static {
initIDs();
}
}
上面附上了两个类的源码,并附上了一些简单的注释。下面我们就用具体的例子来加深我们对这两个类的理解。
下面的例子实现了简单的文件复制:
package FileInputOutput;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileIOTest {
public static void main(String[] args) {
byte[] buffer = new byte[1024];
try (FileInputStream fis = new FileInputStream("./src/file/test.txt");
FileOutputStream fos = new FileOutputStream(
"./src/file/testcopy.txt")) {
while (fis.read(buffer) != -1) {
fos.write(buffer);
}
System.out.println("复制完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行上述代码后可以看到在指定目录下复制了一份test.txt文件,文件中内容也被完整复制过来。
以上为本篇内容。