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方法。