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

原创
2017/07/27 14:08
阅读数 116

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之前再次把引用的资源释放干净。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部