您的位置:首页 > 编程语言 > Java开发

【Java部分源码分析之io篇】4.FileOutputStream

2017-07-27 00:00 344 查看
FileOutputStream是OutputStream的子类,同样也是因为它比较常见,用得比较多,所以拿它来分析一下。它的结构与FileInputStream很相似,想看FileInputStream的源码分析可以去看我的另外一篇博文《【Java部分源码分析之io篇】2.FileInputStream》。

先来看看FileOutputStream的成员变量。

/**
* The system dependent file descriptor.
*/
private final FileDescriptor fd;

/**
* True if the file is opened for append.
*/
private final boolean append;

/**
* The associated channel, initialized lazily.
*/
private FileChannel channel;

/**
* The path of the referenced file
* (null if the stream is created with a file descriptor)
*/
private final String path;

private final Object closeLock = new Object();
private volatile boolean closed = false;

fd是文件描述符,它唯一的对应着一个文件,每打开一个文件都会有一个唯一的文件描述符,即使打开的是同一个文件。

append如果这个文件的打开模式是追加模式的话,则它为true。

channel是内置的文件管道,在NIO编程中会用到这个。

path是文件的路径。

closeLockclosed都是运用在文件输出流关闭的时候,两者都是判断条件。如果closeLock被其他对象锁住了,则不能关闭输出流;如果closed为false,也不能关闭输出流。

FileOutputStream有五个重载的构造方法。

public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
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");
}
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;

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);
}

可以看到前面三个构造方法都是调用的第四个构造方法。

第三个构造方法是利用一个file对象来进行构造输出流,先检查一下文件的写权限,路径是否为空,文件是否存在。然后生成一个文件描述符绑定这个输出流,fd.attach(this)的作用就是为了GC的时候跟踪所引用的对象,防止误GC。然后调用本地方法打开文件。open调用的是本地方法,这是在JVM层面上实现的方法。可以这么说,基本上所有与操作系统底层打交道的函数都是直接或间接调用JVM层面实现的函数。

第四个构造方法是利用一个文件描述符进行构造输出流,因为一个文件描述符唯一对应着一个文件,所以可以用来构造文件输出流。它的步骤与上面第三个方法是一致的。

接下来看看它的write方法。

private native void write(int b, boolean append) throws IOException;
public void write(int b) throws IOException {
write(b, append);
}
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
}
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
}

可以看到关键的write和writeBytes方法都是调用本地方法实现的,其他的重载函数都是调用它们得来的。

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();
}
});
}

来看FileInputStream的close方法。这个方法有个同步块,所以在多线程环境下的close操作是安全的。最后的方法体中的close0是一个本地方法,具体实现在JVM层面上,应该就是释放系统资源之类的。close0的声明如下:

private native void close0() throws IOException;

我们再来看看finalize方法。

protected void finalize() throws IOException {
if (fd != null) {
if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
flush();
} else {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
}

一般来说,类方法是很少有这个finalize方法的,这个方法主要在Object方法中实现,这里重写这个方法主要是为了防止GC的时候还有资源没有释放干净,在GC之前再次把引用的资源释放干净。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息