FileInputStream和RandomAccessFile的源码分析和使用方法详细分析(windows操作系统,JDK8)
、FileInputStream的源码分析和使用方法详细分析FileInputStream 是 Java IO 体系中文件读取的基础类通过封装操作系统的文件操作提供了简单易用的字节流读取接口。其设计融合了模板方法模式统一接口、适配器模式屏蔽系统差异和代理模式资源生命周期管理是面向对象设计原则的典型实践FileInputStream 适用于二进制文件读取如图片、音频、视频等非文本文件字符文件建议使用 FileReader。FileInputStream 的在操作文件时需要与FileDescriptor文件操作符相关联关于FileDescriptor文件操作符可以查看我的另一篇博客5、FileDescriptor的源码和使用注意事项windows操作系统JDK8FileInputStream.class::getChannel() 函数可以将当前的FileInputStream对象与NIO中的FileChannel相关联从而获得更高效的文件操作如内存映射、分散/聚集读取。使用完FileInputStream之后必须显式调用 close() 函数释放文件句柄或使用 try-with-resources 自动关闭避免资源泄漏。FileInputStream不是线程安全的在多线程下使用FileInputStream时需要注意线程安全的问题。FileInputStream的UML关系图如下所示FileInputStream.class的源码如下import java.nio.channels.FileChannel; import sun.nio.ch.FileChannelImpl; public class FileInputStream extends InputStream { //文件描述符 private final FileDescriptor fd; //在构造函数中通过File对象获取文件的路径 private final String path; //NIO中的FileChannel可以更高效的对文件进行操作在getChannel() 函数中将当前的FileInputStream对象与FileChannel相关联 private FileChannel channel null; //在close()函数中关闭当前这个FileInputStream的函数用于线程同步的锁对象 private final Object closeLock new Object(); private volatile boolean closed false;//当前这个FileInputStream是否关闭的标记 //构造函数入参是文件的路径名比如要传入windows操作系统中D盘下的nio_data.txt文件路径时入参为D:\nio_data.txt public FileInputStream(String name) throws FileNotFoundException { this(name ! null ? new File(name) : null);//通过文件的路径名构造的FileInputStream对象时最终都要将文件的路径名构造为File对象 } //构造函数通过File对象构造FileInputStream对象 public FileInputStream(File file) throws FileNotFoundException { String name (file ! null ? file.getPath() : null); SecurityManager security System.getSecurityManager(); if (security ! null) { security.checkRead(name); // 读权限检查防止当前线程读取未授权的文件 } if (name null) { throw new NullPointerException();//文件的路径名null时抛出一个NullPointerException } if (file.isInvalid()) { throw new FileNotFoundException(Invalid file path);//File对象的isInvalid()函数返回true时抛出一个FileNotFoundException } fd new FileDescriptor(); fd.attach(this);// 将当前这个FileInputStream与文件描述符绑定用于资源回收 path name; open(name);// 最终调用本地函数native修饰open0()打开文件 } //构造函数通过FileDescriptor对象构造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);// 将当前这个FileInputStream与文件描述符绑定用于资源回收 } //native修饰的本地函数当前这个FileInputStream对象打开1个指定文件并为了之后的read()函数、readBytes()函数、skip()函数做准备 private native void open0(String name) throws FileNotFoundException; private void open(String name) throws FileNotFoundException { open0(name); } public int read() throws IOException { return read0(); } //native修饰的本地函数当前这个FileInputStream对象从1个指定文件中每次读取1个字节 //如果没有读取到这个文件的末尾则返回读取到的这1个字节的ASCII编码如果已经读取到了这个文件的末尾则返回-1 private native int read0() throws IOException; //native修饰的本地函数当前这个FileInputStream对象从1个指定文件中每次读取len个字节到byte b[]数组的[off,offlen)索引位置 //如果没有读取到这个文件的末尾则返回已经读取到的byte b[]数组中的累计字节数量所有累计读取到的字节都是通过ASCII编码的如果已经读取到了这个文件的末尾则返回-1 private native int readBytes(byte b[], int off, int len) throws IOException; 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); } public long skip(long n) throws IOException { return skip0(n); } //native修饰的本地函数通过这个函数当前这个FileInputStream对象可以从1个指定文件跳过n个字节再进行后续操作比如通过read()函数读取 private native long skip0(long n) throws IOException; public int available() throws IOException { return available0(); } //native修饰的本地函数返回与当前这个FileInputStream对象关联的1个指定文件还可以进行后续操作比如read()函数和skip()函数的字节数量 private native int available0() throws IOException; public void close() throws IOException { synchronized (closeLock) {// 通过synchronized 防止多线程重复关闭 if (closed) { return; } closed true;//第一个执行到这里的线程先设置boolean closed true防止后面执行close() 函数的线程还要继续执行后续的代码片段 } //如果NIO中的FileChannel对象与这个FileInputStream对象相关联同时关闭NIO中FileChannel对象 if (channel ! null) { channel.close(); } // 释放文件描述符FileDescriptor对象关联的所有资源 fd.closeAll(new Closeable() { public void close() throws IOException { close0();//调用本地函数关闭文件 } }); } //获取文件描述符FileDescriptor对象 public final FileDescriptor getFD() throws IOException { if (fd ! null) { return fd; } throw new IOException(); } //将当前这个FileInputStream对象与NIO中的FileChannel相关联 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(); } //重写了Object.class的finalize()函数这个函数会在对象销毁时由JVM自动调用 //不建议当前这个FileInputStream对象销毁时通过finalize()函数调用close()函数来关闭当前这个FileInputStream对象建议在使用完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(); } } }1.1、FileInputStream.class的skip()函数和available()函数关于read()函数和read(byte b[])函数的使用方式可以查看我的另一篇博客1、Java的IO概览一我的windows操作系统的D盘根目录下有nio-data.txt文件如下所示通过skip()函数可以从这个nio-data.txt文件跳过n个字节再进行后续操作通过available()函数可以返回与这个FileInputStream对象关联的nio-data.txt文件还可以进行后续操作的字节数量如下所示package com.xxx.bio; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileInputStreamTest { public static void main(String[] args) throws IOException { InputStream is new FileInputStream(D:\\nio-data.txt); System.out.println(is.available()); is.skip(5); System.out.println(is.available()); } }程序运行结果如下所示1.2、模板方法模式InputStream 作为抽象基类定义了 read()、skip()、available() 等抽象方法 FileInputStream、需实现这些方法以提供具体功能。例如// InputStream 中的抽象方法 public abstract int read() throws IOException; // FileInputStream 中的具体实现 public int read() throws IOException { return read0(); // 调用本地方法实现 }通过模板方法模式统一了字节流读取的接口子类只需关注具体读取逻辑提高了代码的可扩展性。1.3、适配器模式Adapter Pattern文件读取的底层操作依赖操作系统如 Linux 的 read() 系统调用FileInputStream 通过 native 方法将这些系统调用封装为 Java 接口如 read()、close()使得上层代码无需关心具体操作系统的差异。private native int read0() throws IOException; // 适配系统级读取操作 private native void close0() throws IOException; // 适配系统级关闭操作通过适配器模式屏蔽了底层系统的复杂性提供了跨平台的统一文件读取接口。1.4、代理模式Proxy PatternFileDescriptor 是系统级文件句柄的代理对象FileInputStream 通过 fd.attach(this) 将自身与描述符绑定。当流关闭时描述符会触发资源释放如 close0()。这种设计使得多个流可以共享同一个描述符如通过 getFD() 获取但最终由最后一个关联的流负责关闭。fd.attach(this); // 将流与描述符绑定 fd.closeAll(/* 关闭回调 */); // 所有关联的流关闭后释放资源通过代理模式实现了文件句柄的生命周期管理避免资源泄漏。二、RandomAccessFile的源码分析和使用方法详细分析RandomAccessFile 是 Java IO 体系中文件读取的基础类用于在文件中的任何位置进行读写操作。RandomAccessFile实现了DataOutput.interface和DataInput.interface两个接口拥有读取和写入java基本数据类型byteshortintlongdoublefloatbooleanchar 和UTF-8字符串方法,有效地与IO流继承体系中其他部分实现了分离由于不支持装饰者设计模式所以不能与OutputStream和InputStream的子类结合起来使用。RandomAccessFile之所以说对文件随机访问其原理是将文件看成是一个大型的字节数组然后通过游标(cursor)或者移动文件指针可以理解为数组中索引对数组中任意位置字节读取或者写入从而做到对文件的随机访问RandomAccessFile构造方法中会传入对应的读写模式,共有4种。如下所示读写模式解释r以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。rw打开以便读取和写入。rws打开以便读取和写入。相对于 rwrws 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。rwd打开以便读取和写入相对于 rwrwd 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等)rw 、rws 、rwd之间才有区别区别如下①、当模式是 rws 并且 操作的是基础存储设备上的文件那么每次“更改文件内容如执行write()函数” 或 “修改文件元数据(如文件的mtime)”时都会将这些改变同步到基础存储设备上②、当模式是 rwd 并且操作的是基础存储设备上的文件那么每次“更改文件内容如执行write()函数”时都会将这些改变同步到基础存储设备上③、当模式是 rw 并且 操作的是基础存储设备上的文件那么关闭文件时会将“文件内容的修改”同步到基础存储设备上。至于“更改文件内容”时是否会立即同步取决于系统底层实现。RandomAccessFile的UML关系图如下所示RandomAccessFile.class的源码如下package java.io; import java.nio.channels.FileChannel; import sun.nio.ch.FileChannelImpl; public class RandomAccessFile implements DataOutput, DataInput, Closeable { //文件描述符 private FileDescriptor fd; //NIO中的FileChannel可以更高效的对文件进行操作在getChannel() 函数中将当前的FileInputStream对象与FileChannel相关联 private FileChannel channel null; private boolean rw; //在构造函数中通过File对象获取的文件路径保存在该变量中 private final String path; //在close()函数中关闭当前这个RandomAccessFile 对象的函数用于线程同步的锁对象 private Object closeLock new Object(); private volatile boolean closed false;//当前这个RandomAccessFile 是否关闭的标记 //只读模式不具备写权限如果文件不存在不会创建文件。 private static final int O_RDONLY 1; //读写模式具备读写权限如果文件不存在会创建文件该模式下数据改变时不会立马写入底层存储设备。 private static final int O_RDWR 2; //同步的读写模式具备读写模式的所有特性当文件内容或元数据改变时会立马同步写入到底层存储设备中。 private static final int O_SYNC 4; //同步的读写模式具备读写模式的所有特性当文件内容改变时会立马同步写入到底层存储设备中。 private static final int O_DSYNC 8; public RandomAccessFile(String name, String mode) throws FileNotFoundException { this(name ! null ? new File(name) : null, mode); } public RandomAccessFile(File file, String mode) throws FileNotFoundException { // 定义了一个String类型变量name用于接收操作文件的路径名如果文件为null则name赋值为null。 String name (file ! null ? file.getPath() : null); int imode -1; if (mode.equals(r)) imode O_RDONLY;//情况1字符串modelr时imode1 else if (mode.startsWith(rw)) { imode O_RDWR;//情况2字符串model以rw开头时可以为rws或者rwd也可以为rw任何字符串imode2 rw true; if (mode.length() 2) { if (mode.equals(rws)) imode | O_SYNC;//情况3字符串modelrws时imode6 else if (mode.equals(rwd)) imode | O_DSYNC;//情况4字符串modelrwd时imode10 else imode -1;//model不属于情况1、2、3、4的其余情况imode-1 } } if (imode 0)//model不属于情况1、2、3、4的其余情况时抛出一个IllegalArgumentException throw new IllegalArgumentException(Illegal mode \ mode \ must be one of \r\, \rw\, \rws\, or \rwd\); //获得java的安全管理器根据rw的状态监测文件的读写权限。 SecurityManager security System.getSecurityManager(); if (security ! null) { security.checkRead(name); if (rw) { security.checkWrite(name); } } if (name null) { throw new NullPointerException();//文件的路径名null时抛出一个NullPointerException } if (file.isInvalid()) { //File对象的isInvalid()函数返回true时抛出一个FileNotFoundException throw new FileNotFoundException(Invalid file path); } fd new FileDescriptor(); fd.attach(this);// 将当前这个RandomAccessFile与文件描述符绑定用于资源回收 path name; open(name, imode);// 最终调用本地函数native修饰open0()打开文件 } //获取文件描述符FileDescriptor对象 public final FileDescriptor getFD() throws IOException { if (fd ! null) { return fd; } throw new IOException(); } //将当前这个RandomAccessFile对象与NIO中的FileChannel相关联 public final FileChannel getChannel() { synchronized (this) { if (channel null) { channel FileChannelImpl.open(fd, path, true, rw, this); } return channel; } } //native修饰的本地函数当前这个RandomAccessFile对象用指定的读写模式打开1个指定文件并为了之后的read()函数、readBytes()函数、seek()函数做准备 private native void open0(String name, int mode) throws FileNotFoundException; private void open(String name, int mode) throws FileNotFoundException { open0(name, mode); } public int read() throws IOException { return read0(); } //native修饰的本地函数当前这个RandomAccessFile对象从1个指定文件中每次读取1个字节 //如果没有读取到这个文件的末尾则返回读取到的这1个字节的ASCII编码如果已经读取到了这个文件的末尾则返回-1 private native int read0() throws IOException; //native修饰的本地函数当前这个RandomAccessFile对象从1个指定文件中每次读取len个字节到byte b[]数组的[off,offlen)索引位置 //如果没有读取到这个文件的末尾则返回已经读取到的byte b[]数组中的累计字节数量所有累计读取到的字节都是通过ASCII编码的如果已经读取到了这个文件的末尾则返回-1 private native int readBytes(byte b[], int off, int len) throws IOException; public int read(byte b[], int off, int len) throws IOException { return readBytes(b, off, len); } public int read(byte b[]) throws IOException { return readBytes(b, 0, b.length); } public final void readFully(byte b[]) throws IOException { readFully(b, 0, b.length); } //从打开的文件中读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 public final void readFully(byte b[], int off, int len) throws IOException { int n 0;//累计读取到byte[]数组b中的字节数量 do { //count-1时表示读取到了文件的末尾。 //count0时表示从本次打开的文件中读取了(len - n)个字节到byte[]数组b的[off n,off len)索引位置。 int count this.read(b, off n, len - n); if (count 0) throw new EOFException(); n count;//累加读到的字节总数量 } while (n len);//当累计读到的字节总数量len时跳出循环 } //当前这个RandomAccessFile对象可以从1个指定文件大型的字节数组中跳过n个字节n文件的字节总数量再进行后续操作比如通过read()函数读取 public int skipBytes(int n) throws IOException { long pos;//将文件看成是一个大型的字节数组时pos表示当前操作该数组时所处的索引位置 long len;//将文件看成是一个大型的字节数组时len表示数组的长度 long newpos;//将文件看成是一个大型的字节数组时newpos表示要跳跃重新指向到的索引位置 if (n 0) { return 0; } pos getFilePointer();//获取RandomAccessFile对象正在操作的文件大型的字节数组的索引位置 len length();//获取文件大型的字节数组的长度 newpos pos n;//计算要跳跃重新指向到的索引位置 if (newpos len) { newpos len;//如果索引越界了将索引置为文件大型的字节数组的最后一个索引位置 } seek(newpos);//通过native修饰的函数将文件大型的字节数组的的索引置为newpos /* return the actual number of bytes skipped */ return (int) (newpos - pos);//返回实际跳跃的字节数量 } public void write(int b) throws IOException { write0(b); } //native修饰的本地函数当前这个RandomAccessFile对象向1个指定文件中写入1个字节 private native void write0(int b) throws IOException; //native修饰的本地函数当前这个RandomAccessFile对象向1个指定文件中每次写入byte b[]数组中[off,offlen)索引位置的字节 private native void writeBytes(byte b[], int off, int len) throws IOException; public void write(byte b[]) throws IOException { writeBytes(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { writeBytes(b, off, len); } //native修饰的本地函数将文件看成是一个大型的字节数组时该函数返回当前操作读或者写到该数组的索引位置 public native long getFilePointer() throws IOException; public void seek(long pos) throws IOException { if (pos 0) { throw new IOException(Negative seek offset); } else { seek0(pos); } } //native修饰的本地函数将文件看成是一个大型的字节数组时该函数可以跳跃到该数组的指定索引位置 private native void seek0(long pos) throws IOException; //native修饰的本地函数将文件看成是一个大型的字节数组时该函数可以获得这个数组的长度 public native long length() throws IOException; //native修饰的本地函数将文件看成是一个大型的字节数组时该函数可以设置这个数组的长度 public native void setLength(long newLength) throws IOException; public void close() throws IOException { synchronized (closeLock) {// 通过synchronized 防止多线程重复关闭 if (closed) { return; } closed true;//第一个执行到这里的线程先设置boolean closed true防止后面执行close() 函数的线程还要继续执行后续的代码片段 } //如果NIO中的FileChannel对象与这个FileInputStream对象相关联同时关闭NIO中FileChannel对象 if (channel ! null) { channel.close(); } // 释放文件描述符FileDescriptor对象关联的所有资源 fd.closeAll(new Closeable() { public void close() throws IOException { close0();//调用本地函数关闭文件 } }); } //读取1个boolean类型数据