FileInputStream与FileOutputStream源码分析

FileInputStream 

package java.io;

import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;

//FileInputStream是文件输入流,用于从文件系统中的某个文件中获得输入字节。哪些文件可用依赖于主机环境
//FileInputStream用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用FileReader。
public class FileInputStream extends InputStream
{
    //文件描述符,处理打开的文件
    private final FileDescriptor fd;

    //引用文件的路径
    private final String path;

    //文件通道
    private FileChannel channel = null;
    //关闭锁
    private final Object closeLock = new Object();
    //标识流是否关闭
    private volatile boolean closed = false;

     /**
     * 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定。
     * 创建一个新FileDescriptor对象来表示此文件连接。
     * 
     * 首先,如果有安全管理器,则用name作为参数调用其checkRead方法。
     *
     * @param      name   文件系统中的路径名
     * @exception  FileNotFoundException  如果该文件不存在,或者它是一个目录,而不是一个常规文件,
     * 抑或因为其他某些原因而无法打开进行读取。
     */
    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();
        //如果有安全管理器,则调用其checkRead方法,检查是否允许读
        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);
    }

    /**
     * 通过使用文件描述符fdObj创建一个FileInputStream,
     * 该文件描述符表示到文件系统中某个实际文件的现有连接。
     */
    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);
    }

     /*
     * native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。
     * Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
     * JNI是Java本机接口(Java Native Interface),是一个本机编程接口,
     * 它是Java软件开发工具箱(Java Software Development Kit,SDK)的一部分。
     * JNI允许Java代码使用以其他语言编写的代码和代码库。
     * Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,
     * 从而允许程序员从本机代码内部调用Java代码。
     * 所以想要了解open0方法的具体实现只能去查看JVM源码了。
     */
    private native void open0(String name) throws FileNotFoundException;

    // wrap native call to allow instrumentation
    /**
     * Opens the specified file for reading.
     * @param name the name of the file
     */
    private void open(String name) throws FileNotFoundException {
        open0(name);
    }

    /**
     * 从此输入流中读取一个数据字节,该方法阻塞直到输入数据可用
     * @return 数据的下一个字节,或者-1(如果到文件尾部)
     */
    public int read() throws IOException {
        return read0();
    }

    private native int read0() throws IOException;

    /**
     * 从此输入流中将最多len个字节的数据读入一个起始偏移量为off的byte数组中。
     * @param b 数据被写入的字节数组
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @exception IOException If an I/O error has occurred.
     */
    private native int readBytes(byte b[], int off, int len) throws IOException;

    //从此输入流中将最多b.length个字节的数据读入一个起始偏移量为0的byte数组中
    //该方法阻塞直到输入数据可用
    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }

    //从此输入流中将最多len个字节的数据读入一个起始偏移量为off的byte数组中
    //该方法阻塞直到输入数据可用
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }

    /**
     * Skips over and discards <code>n</code> bytes of data from the
     * input stream.
     *
     * <p>The <code>skip</code> method may, for a variety of
     * reasons, end up skipping over some smaller number of bytes,
     * possibly <code>0</code>. If <code>n</code> is negative, the method
     * will try to skip backwards. In case the backing file does not support
     * backward skip at its current position, an <code>IOException</code> is
     * thrown. The actual number of bytes skipped is returned. If it skips
     * forwards, it returns a positive value. If it skips backwards, it
     * returns a negative value.
     *
     * <p>This method may skip more bytes than what are remaining in the
     * backing file. This produces no exception and the number of bytes skipped
     * may include some number of bytes that were beyond the EOF of the
     * backing file. Attempting to read from the stream after skipping past
     * the end will result in -1 indicating the end of the file.
     *
     * @param      n   the number of bytes to be skipped.
     * @return     the actual number of bytes skipped.
     * @exception  IOException  if n is negative, if the stream does not
     *             support seek, or if an I/O error occurs.
     */
    public long skip(long n) throws IOException {
        return skip0(n);
    }

    private native long skip0(long n) throws IOException;

    //返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
    //下一个调用可能是同一线程或另一个线程,仅读取或跳过此多个字节不会阻塞,但可能会读取或跳过较少的字节。
    //在某些情况下,非阻塞读取(或跳过)可能仅在速度较慢时(例如,在速度较慢的网络上读取大文件时)似乎已被阻塞
    public int available() throws IOException {
        return available0();
    }

    private native int available0() throws IOException;

    /**
     * Closes this file input stream and releases any system resources
     * associated with the stream.
     *
     * <p> If this stream has an associated channel then the channel is closed
     * as well.
     *
     * @exception  IOException  if an I/O error occurs.
     *
     * @revised 1.4
     * @spec JSR-51
     */
    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();
           }
        });
    }

    /**
     * Returns the <code>FileDescriptor</code>
     * object  that represents the connection to
     * the actual file in the file system being
     * used by this <code>FileInputStream</code>.
     *
     * @return     the file descriptor object associated with this stream.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileDescriptor
     */
    public final FileDescriptor getFD() throws IOException {
        if (fd != null) {
            return fd;
        }
        throw new IOException();
    }

    /**
     * Returns the unique {@link java.nio.channels.FileChannel FileChannel}
     * object associated with this file input stream.
     *
     * <p> The initial {@link java.nio.channels.FileChannel#position()
     * position} of the returned channel will be equal to the
     * number of bytes read from the file so far.  Reading bytes from this
     * stream will increment the channel's position.  Changing the channel's
     * position, either explicitly or by reading, will change this stream's
     * file position.
     *
     * @return  the file channel associated with this file input stream
     *
     * @since 1.4
     * @spec JSR-51
     */
    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, false, this);
            }
            return channel;
        }
    }

    private static native void initIDs();

    private native void close0() throws IOException;

    static {
        initIDs();
    }

    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* 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();
        }
    }
}

FileOutputStream 

public class FileOutputStream extends OutputStream
{
    /**
     * 文件描述符
     */
    private final FileDescriptor fd;

    /**
     * 标识添加还是替换文件的内容
     */
    private final boolean append;

    /**
     * 关联的通道
     * 懒加载
     */
    private FileChannel channel;

    //引用文件的路径
    private final String path;

    //锁
    private final Object closeLock = new Object();
    //标识流是否关闭
    private volatile boolean closed = false;

    /**
     * 创建一个向具有指定名称的文件中写入数据的输出文件流。
     * 创建一个新FileDescriptor对象来表示此文件连接。
     * 首先,如果有安全管理器,则用 name 作为参数调用checkWrite方法。
     *
     * @param      name   文件路径
     * @exception  FileNotFoundException  如果文件存在,但它是一个目录,而不是一个常规文件;或者该文件不存在,但无法创建它;抑或因为其他某些原因而无法打开它
     * @exception  SecurityException   如果存在安全管理器,且其checkWrite方法拒绝对文件进行写入访问
     */
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

    /**
     * 创建一个向具有指定name的文件中写入数据的输出文件流。如果第二个参数为append为true,则将字节写入文件末尾处,而不是写入文件开始处。
     */
    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

    /**
     * 创建一个向指定File对象表示的文件中写入数据的文件输出流。创建一个新FileDescriptor对象来表示此文件连接。
     */
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    /**
     * 创建一个向指定File对象表示的文件中写入数据的文件输出流。如果第二个参数append为true,则将字节写入文件末尾处,而不是写入文件开始处。     */
    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);
    }

    //参考FileInputStream.open0(String name)
    private native void open0(String name, boolean append)
        throws FileNotFoundException;

    private void open(String name, boolean append)
        throws FileNotFoundException {
        open0(name, append);
    }

    /**
     * 将指定字节写入此文件输出流。
     * 参考FileInputStream.open0(String name)
     */
    private native void write(int b, boolean append) throws IOException;

    /**
     * 将指定字节写入此文件输出流。
     */
    public void write(int b) throws IOException {
        write(b, append);
    }

    /**
     * 将b.length个字节从指定byte数组写入此文件输出流中
     * 参考FileInputStream.open0(String name)
     */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

    /**
     * 将指定byte数组中从偏移量off开始的len个字节写入此文件输出流。
     */
    public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append);
    }

    /**
     * 将指定byte数组中从偏移量off开始的len个字节写入此文件输出流。
     */
    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();
     }

    /**
     * 返回与此文件输出流有关的唯一FileChannel对象。
     */
    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, false, true, append, this);
            }
            return channel;
        }
    }

    /**
     * 清理到文件的连接,并确保在不再引用此文件输出流时调用此流的close方法。
     */
    protected void finalize() throws IOException {
        if (fd != null) {
            if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
                flush();
            } else {
                close();
            }
        }
    }

    参考FileInputStream.open0(String name)
    private native void close0() throws IOException;

    //参考FileInputStream.open0(String name)
    private static native void initIDs();

    static {
        initIDs();
    }

}

 总结

  • FileInputStream是文件输入流,用于从文件系统中的某个文件中获得输入字节。FileInputStream用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用FileReader。
  • FileOutputStream是文件输出流,用于将数据写入File或FileDescriptor的输出流。FileOutputStream用于写入诸如图像数据之类的原始字节的流。要写入字符流,请考虑使用FileWriter。
  • Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
  • FileInputStream不支持mark方法与set方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值