当前位置: 首页 > news >正文

10、FileInputStream和RandomAccessFile的源码分析和使用方法详细分析(windows操作系统,JDK8)

一、FileInputStream的源码分析和使用方法详细分析

  FileInputStream 是 Java IO 体系中文件读取的基础类,通过封装操作系统的文件操作,提供了简单易用的字节流读取接口。其设计融合了模板方法模式(统一接口)、适配器模式(屏蔽系统差异)和代理模式(资源生命周期管理),是面向对象设计原则的典型实践
FileInputStream 适用于二进制文件读取:如图片、音频、视频等非文本文件(字符文件建议使用 FileReader)。FileInputStream 的在操作文件时,需要与FileDescriptor(文件操作符)相关联,关于FileDescriptor(文件操作符),可以查看我的另一篇博客:5、FileDescriptor的源码和使用注意事项(windows操作系统,JDK8)
FileInputStream.class::getChannel() 函数可以将当前的FileInputStream对象与NIO中的FileChannel相关联,从而获得更高效的文件操作(如内存映射、分散/聚集读取)。
使用完FileInputStream之后,必须显式调用 close() 函数释放文件句柄(或使用 try-with-resources 自动关闭),避免资源泄漏。FileInputStream不是线程安全的,在多线程下使用FileInputStream时,需要注意线程安全的问题。
FileInputStream的UML关系图,如下所示:
clipboard

  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编码,如果已经读取到了这个文件的末尾,则返回-1private native int read0() throws IOException;//native修饰的本地函数,当前这个FileInputStream对象从1个指定文件中每次读取len个字节到byte b[]数组的[off,off+len)索引位置;//如果没有读取到这个文件的末尾,则返回已经读取到的byte b[]数组中的累计字节数量(所有累计读取到的字节都是通过ASCII编码的),如果已经读取到了这个文件的末尾,则返回-1private 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文件,如下所示:
clipboard

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

程序运行结果,如下所示:
clipboard

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 Pattern)

  FileDescriptor 是系统级文件句柄的代理对象,FileInputStream 通过 fd.attach(this) 将自身与描述符绑定。当流关闭时,描述符会触发资源释放(如 close0())。这种设计使得多个流可以共享同一个描述符(如通过 getFD() 获取),但最终由最后一个关联的流负责关闭。

fd.attach(this);  // 将流与描述符绑定
fd.closeAll(/* 关闭回调 */);  // 所有关联的流关闭后释放资源

通过代理模式,实现了文件句柄的生命周期管理,避免资源泄漏。

二、RandomAccessFile的源码分析和使用方法详细分析

  RandomAccessFile 是 Java IO 体系中文件读取的基础类,用于在文件中的任何位置进行读写操作。RandomAccessFile实现了DataOutput.interface和DataInput.interface两个接口,拥有读取和写入java基本数据类型(byte,short,int,long,double,float,boolean,char) 和UTF-8字符串方法,有效地与IO流继承体系中其他部分实现了分离,由于不支持装饰者设计模式,所以不能与OutputStream和InputStream的子类结合起来使用。RandomAccessFile之所以说对文件随机访问,其原理是将文件看成是一个大型的字节数组,然后通过游标(cursor)或者移动文件指针(可以理解为数组中索引对数组中任意位置字节读取或者写入),从而做到对文件的随机访问,RandomAccessFile构造方法中会传入对应的读写模式,共有4种。如下所示:

读写模式 解释
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。
"rws" 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
"rwd" 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等),"rw" 、"rws" 、"rwd"之间才有区别,区别如下:
①、当模式是 "rws" 并且 操作的是基础存储设备上的文件;那么,每次“更改文件内容(如执行write()函数)” 或 “修改文件元数据(如文件的mtime)”时,都会将这些改变同步到基础存储设备上;
②、当模式是 "rwd" 并且操作的是基础存储设备上的文件;那么,每次“更改文件内容(如执行write()函数)”时,都会将这些改变同步到基础存储设备上;
③、当模式是 "rw" 并且 操作的是基础存储设备上的文件;那么,关闭文件时,会将“文件内容的修改”同步到基础存储设备上。至于,“更改文件内容”时,是否会立即同步,取决于系统底层实现。
RandomAccessFile的UML关系图,如下所示:
clipboard

  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:字符串model=r时imode=1else if (mode.startsWith("rw")) {imode = O_RDWR;//情况2:字符串model以rw开头时(可以为rws或者rwd,也可以为rw+任何字符串),imode=2rw = true;if (mode.length() > 2) {if (mode.equals("rws"))imode |= O_SYNC;//情况3:字符串model=rws时imode=6else if (mode.equals("rwd"))imode |= O_DSYNC;//情况4:字符串model=rwd时imode=10elseimode = -1;//model不属于情况1、2、3、4的其余情况,imode=-1}}if (imode < 0)//model不属于情况1、2、3、4的其余情况时,抛出一个IllegalArgumentExceptionthrow 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时,抛出一个FileNotFoundExceptionthrow 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编码,如果已经读取到了这个文件的末尾,则返回-1private native int read0() throws IOException;//native修饰的本地函数,当前这个RandomAccessFile对象从1个指定文件中每次读取len个字节到byte b[]数组的[off,off+len)索引位置;//如果没有读取到这个文件的末尾,则返回已经读取到的byte b[]数组中的累计字节数量(所有累计读取到的字节都是通过ASCII编码的),如果已经读取到了这个文件的末尾,则返回-1private 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,off+len)索引位置。public final void readFully(byte b[], int off, int len) throws IOException {int n = 0;//累计读取到byte[]数组b中的字节数量do {//count=-1时,表示读取到了文件的末尾。//count>0时,表示从本次打开的文件中读取了(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,off+len)索引位置的字节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类型数据public final boolean readBoolean() throws IOException {int ch = this.read();//boolean类型占一个字节,调用1次native修饰的read0()函数即可读取1个字节if (ch < 0)throw new EOFException();return (ch != 0);}//读取1个byte类型数据public final byte readByte() throws IOException {int ch = this.read();//byte类型占1个字节,调用1次native修饰的read0()函数即可读取1个字节if (ch < 0)throw new EOFException();return (byte)(ch);}//读取1个无符号的byte类型数据public final int readUnsignedByte() throws IOException {int ch = this.read();//无符号的byte类型占1个字节,调用1次native修饰的read0()函数即可if (ch < 0)throw new EOFException();return ch;}//读取1个short类型数据public final short readShort() throws IOException {int ch1 = this.read();//short类型占2个字节,占16位(bit),第1次调用native修饰的read0()函数读取的是高8位(bit)表示的1个字节int ch2 = this.read();//第2次调用native修饰的read0()函数读取的是低8位(bit)表示的1个字节if ((ch1 | ch2) < 0)throw new EOFException();return (short)((ch1 << 8) + (ch2 << 0));//高8位(bit)和低8位(bit)相加,组合成2字节(占16bit位)的short类型数据}//读取1个无符号的short类型数据public final int readUnsignedShort() throws IOException {int ch1 = this.read();//无符号的short类型占2个字节,占16位(bit),第1次调用native修饰的read0()函数读取的是高8位(bit)表示的1个字节int ch2 = this.read();//第2次调用native修饰的read0()函数读取的是低8位(bit)表示的1个字节if ((ch1 | ch2) < 0)throw new EOFException();return (ch1 << 8) + (ch2 << 0);//高8位(bit)和低8位(bit)相加,组合成2字节(占16bit位)的无符号short类型数据}//读取1个char类型数据public final char readChar() throws IOException {int ch1 = this.read();//char类型占2个字节,占16位(bit),第1次调用native修饰的read0()函数读取的是高8位(bit)表示的1个字节int ch2 = this.read();//第2次调用native修饰的read0()函数读取的是低8位(bit)表示的1个字节if ((ch1 | ch2) < 0)throw new EOFException();return (char)((ch1 << 8) + (ch2 << 0));//高8位(bit)和低8位(bit)相加,组合成2字节(占16bit位)的char类型数据}//读取1个int类型数据public final int readInt() throws IOException {int ch1 = this.read();//int类型占4个字节,占32位(bit),第1次调用native修饰的read0()函数读取的是第1个8位(bit)表示的第1个字节int ch2 = this.read();//第2次调用native修饰的read0()函数读取的是第2个8位(bit)表示的第2个字节int ch3 = this.read();//第3次调用native修饰的read0()函数读取的是第3个8位(bit)表示的第3个字节int ch4 = this.read();//第4次调用native修饰的read0()函数读取的是第4个8位(bit)表示的第4个字节if ((ch1 | ch2 | ch3 | ch4) < 0)throw new EOFException();return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));//4个字节相加,组合成了4字节(占32bit位)的int类型数据}//读取1个long类型数据,long类型占8个字节,占64位(bit)public final long readLong() throws IOException {//先读4个字节作为高32位(bit),再读4个字节作为低32位(bit),高32位(bit)和低32位(bit)相加,组合成了8字节(占64bit位)的long类型数据return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);}//读取1个float类型数据public final float readFloat() throws IOException {return Float.intBitsToFloat(readInt());}//读取1个double类型数据public final double readDouble() throws IOException {return Double.longBitsToDouble(readLong());}//一次读取一行数据,并将读取到的数据返回。public final String readLine() throws IOException {//创建了一个StringBuffer对象,用于接收读取的数据。StringBuffer input = new StringBuffer();//声明了一个int型变量c,用于接收读取的数据,声明了一个boolean型变量eol,表明是否读取到了换行符。int c = -1;boolean eol = false;//通过一个循环来不断读取数据。while (!eol) {switch (c = read()) {case -1://返回-1表示文件已经读取完毕。case '\n':eol = true;//返回'\n',表示读到换行符,此时将eol置为true,跳出循环。break;case '\r':eol = true;//返回'\r',将eol置为true,因为不同平台换行符不同,向后读取看是否有'\n'(windows操作系统的换行符为"\r\n"),如果没有,则将操作文件(大型的字节数组)的指针(索引)重置为'\r'的下一个位置,然后跳出循环。long cur = getFilePointer();if ((read()) != '\n') {seek(cur);}break;default:input.append((char)c);//每次操作向StringBuffer input中添加读取到的字节。break;}}//如果没有读取到任何数据,则返回null。if ((c == -1) && (input.length() == 0)) {return null;}//执行到此处时,表示读取到了当前行的数据,最终将input转化成String类型然后返回。return input.toString();}public final String readUTF() throws IOException {return DataInputStream.readUTF(this);}//向RandomAccessFile对象正在操作的文件中写入1个boolean类型数据public final void writeBoolean(boolean v) throws IOException {write(v ? 1 : 0);//written++;}//向RandomAccessFile对象正在操作的文件中写入1个byte类型数据public final void writeByte(int v) throws IOException {write(v);//written++;}//向RandomAccessFile对象正在操作的文件中写入1个short类型数据//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。public final void writeShort(int v) throws IOException {write((v >>> 8) & 0xFF);//先向文件中写入short类型(占2个字节,共16位)的高8位(bit)write((v >>> 0) & 0xFF);//再向文件中写入short类型(占2个字节,共16位)的低8位(bit)//written += 2;}//向RandomAccessFile对象正在操作的文件中写入1个char类型数据//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。public final void writeChar(int v) throws IOException {//先向文件中写入char类型(占2个字节,共16位)的高8位(bit)write((v >>> 8) & 0xFF);//再向文件中写入char类型(占2个字节,共16位)的低8位(bit)write((v >>> 0) & 0xFF);//written += 2;}//向RandomAccessFile对象正在操作的文件中写入1个int类型数据//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。public final void writeInt(int v) throws IOException {//先向文件中写入int类型(占4个字节,共32位)的第1个8位bit(从左到右)write((v >>> 24) & 0xFF);//再向文件中写入int类型(占4个字节,共32位)的第2个8位bit(从左到右)write((v >>> 16) & 0xFF);//再向文件中写入int类型(占4个字节,共32位)的第3个8位bit(从左到右)write((v >>>  8) & 0xFF);//最后向文件中写入int类型(占4个字节,共32位)的第4个8位bit(从左到右)write((v >>>  0) & 0xFF);//written += 4;}//向RandomAccessFile对象正在操作的文件中写入1个long类型数据//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。public final void writeLong(long v) throws IOException {//先向文件中写入long类型(占8个字节,共64位)的第1个8位bit(从左到右)write((int)(v >>> 56) & 0xFF);//再向文件中写入long类型的第2个8位bit(从左到右)write((int)(v >>> 48) & 0xFF);//再向文件中写入long类型的第3个8位bit(从左到右)write((int)(v >>> 40) & 0xFF);//再向文件中写入long类型的第4个8位bit(从左到右)write((int)(v >>> 32) & 0xFF);//再向文件中写入long类型的第5个8位bit(从左到右)write((int)(v >>> 24) & 0xFF);//再向文件中写入long类型的第6个8位bit(从左到右)write((int)(v >>> 16) & 0xFF);//再向文件中写入long类型的第7个8位bit(从左到右)write((int)(v >>>  8) & 0xFF);//最后向文件中写入long类型的第8个8位bit(从左到右)write((int)(v >>>  0) & 0xFF);//written += 8;}//向RandomAccessFile对象正在操作的文件中写入1个float类型数据public final void writeFloat(float v) throws IOException {writeInt(Float.floatToIntBits(v));}//向RandomAccessFile对象正在操作的文件中写入1个double类型数据public final void writeDouble(double v) throws IOException {writeLong(Double.doubleToLongBits(v));}//向RandomAccessFile对象正在操作的文件中写入1个字符串public final void writeChars(String s) throws IOException {int clen = s.length();//获取这个字符串的长度int blen = 2*clen;//因为Java中的1个字符占2个字节,所以字节数组的长度是字符串长度的2倍byte[] b = new byte[blen];char[] c = new char[clen];s.getChars(0, clen, c, 0);//先将字符串写入字符数组中//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。for (int i = 0, j = 0; i < clen; i++) {//所以先向字节数组中写入每个字符(char类型)的高8位bit,再向字节数组中写入每个字符(char类型)的低8位bitb[j++] = (byte)(c[i] >>> 8);b[j++] = (byte)(c[i] >>> 0);}writeBytes(b, 0, blen);//将转换好的字节数组通过writeBytes()函数写入文件中}public final void writeUTF(String str) throws IOException {DataOutputStream.writeUTF(str, this);}private static native void initIDs();private native void close0() throws IOException;static {initIDs();}
}
2.1、大端字节序‌(Big-Endian)存储在RandomAccessFile 中的写入问题(以writeInt()函数为例)

  writeInt(int v) 函数将一个 int 类型的整数(共 ‌4 个字节‌)按顺序写入文件。由于底层的 write() 函数每次只能写入 1 个字节‌,因此需要将 int 的 4 个字节‌逐个提取并写入‌。源码如下所示:

   //向RandomAccessFile对象正在操作的文件中写入1个int类型数据//由于Java 默认采用大端字节序‌(Big-Endian)存储,即‌最高有效字节(MSB)在前,最低有效字节(LSB)在后‌。public final void writeInt(int v) throws IOException {//先向文件中写入int类型(占4个字节,共32位)的第1个8位bit(从左到右)write((v >>> 24) & 0xFF);//再向文件中写入int类型(占4个字节,共32位)的第2个8位bit(从左到右)write((v >>> 16) & 0xFF);//再向文件中写入int类型(占4个字节,共32位)的第3个8位bit(从左到右)write((v >>>  8) & 0xFF);//最后向文件中写入int类型(占4个字节,共32位)的第4个8位bit(从左到右)write((v >>>  0) & 0xFF);//written += 4;}

①、比如要写入一个int类型的数据1,666,688,888,用二进制表示为0110 0011 0101 0111 1010 0111 0111 1000,当需要写入从左往右的第1个字节0110 0011时,会执行

write((v >>> 24) & 0xFF);

整个过程,,如下所示:

step1:先右移24位
0110 0011 0101 0111 1010 0111 0111 1000 >>> 24
结果为(>>> 是‌无符号右移‌,高位补 0,避免符号扩展干扰):
0000 0000 0000 0000 0000 0000 0110 0011
step2:将剩余的8位与0xFF(1111,1111)进行&运算
0000 0000 0000 0000 0000 0000 0110 0011
&                             1111 1111
结果为:
0110 0011

最终将0110 0011(占1个字节)写入RandomAccessFile对象正在操作的文件中。
②、然后写入从左往右的第2个字节0101 0111时,会执行

write((v >>> 16) & 0xFF);

整个过程,,如下所示:

step1:先右移16位
0110 0011 0101 0111 1010 0111 0111 1000 >>> 16
结果为(>>> 是‌无符号右移‌,高位补 0,避免符号扩展干扰):
0000 0000 0000 0000 0110 0011 0101 0111
step2:将剩余的16位与0xFF(1111,1111)进行&运算
0000 0000 0000 0000 0110 0011 0101 0111
&                             1111 1111
结果为:
0101 0111

最终将0101 0111(占1个字节)写入RandomAccessFile对象正在操作的文件中。
③、然后写入从左往右的第3个字节1010 0111时,会执行

write((v >>>  8) & 0xFF);

整个过程,,如下所示:

step1:先右移8位
0110 0011 0101 0111 1010 0111 0111 1000 >>> 8
结果为:
0000 0000 0110 0011 0101 0111 1010 0111
step2:将剩余的24位与0xFF(1111,1111)进行&运算
0000 0000 0110 0011 0101 0111 1010 0111
&                             1111 1111
结果为:
1010 0111

最终将1010 0111(占1个字节)写入RandomAccessFile对象正在操作的文件中。
④、最后写入从左往右的第4个字节0111 1000时,会执行

write((v >>>  0) & 0xFF);

整个过程,,如下所示:

step1:先右移0位,其实就是原值不变,直接拿来参与运算
0110 0011 0101 0111 1010 0111 0111 1000 >>> 0
结果为(>>> 是‌无符号右移‌,高位补 0,避免符号扩展干扰):
0110 0011 0101 0111 1010 0111 0111 1000
step2:原值与0xFF(1111,1111)进行&运算
0110 0011 0101 0111 1010 0111 0111 1000
&                             1111 1111
结果为:
0111 1000

最终将0111 1000(占1个字节)写入RandomAccessFile对象正在操作的文件中。
其余函数,比如writeByte()函数、writeShort()函数、writeChar()函数...与writeInt()函数的原理相同,只是最终写入的字节数量可能会有所不同,但都是从左到右先依次写入高位字节。

2.2、大端字节序‌(Big-Endian)存储在RandomAccessFile 中的读的问题(以readInt()函数为例)

  readInt() 函数会将1个 int 类型的整数(共 ‌4 个字节‌)从RandomAccessFile 操作的文件中读取出 来。由于底层的 read() 函数每次只能读取 1 个字节‌,因此需要将 int 的 4 个字节‌先逐个读取出来,再将4次调用read() 函数读到的结果移项后再拼接起来。源码如下所示:

    //读取1个int类型数据public final int readInt() throws IOException {int ch1 = this.read();//int类型占4个字节,占32位(bit),第1次调用native修饰的read0()函数读取的是第1个8位(bit)表示的第1个字节int ch2 = this.read();//第2次调用native修饰的read0()函数读取的是第2个8位(bit)表示的第2个字节int ch3 = this.read();//第3次调用native修饰的read0()函数读取的是第3个8位(bit)表示的第3个字节int ch4 = this.read();//第4次调用native修饰的read0()函数读取的是第4个8位(bit)表示的第4个字节if ((ch1 | ch2 | ch3 | ch4) < 0)throw new EOFException();return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));//4个字节相加,组合成了4字节(占32bit位)的int类型数据}

最后一步

(ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)

是将4次调用read() 函数读到的结果按照大端字节序‌(Big-Endian)拼接起来,整个过程如下(与writeInt()函数的执行原理相反):
①、比如读取的1个int类型的数据是1,666,688,888,用二进制表示为0110 0011 0101 0111 1010 0111 0111 1000,将这个数读取到内存后,ch1、ch2、ch3、ch4分别为:

ch1:0110 0011
ch2:0101 0111
ch3:1010 0111
ch4:0111 1000

最后一步的拼接前的移项过程为:

ch1 << 24:0110 0011 0000 0000 0000 0000 0000 0000
ch2 << 16:0000 0000 0101 0111 0000 0000 0000 0000 
ch3 << 8: 0000 0000 0000 0000 1010 0111 0000 0000
ch4 << 0: 0000 0000 0000 0000 0000 0000 0111 1000

拼接过程为:

 0110 0011 0000 0000 0000 0000 0000 0000
+0000 0000 0101 0111 0000 0000 0000 0000 
+0000 0000 0000 0000 1010 0111 0000 0000
+0000 0000 0000 0000 0000 0000 0111 1000
结果为:0110 0011 0101 0111 1010 0111 0111 1000
2.3、RandomAccessFile的使用示例

  在使用RandomAccessFile时读写文件时,可以使用多线程读写一个文件以提高整个文件的读写效率,如下所示:

package com.xxx.bio;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class RandomAccessFileIOTest {public static void main(String[] args) {final File source = new File("F:\\realMe手机文件备份\\照片视频\\VID20260217183653.mp4");final File target = new File("D:\\copy.mp4");int threadNum = (int) Math.ceil(Math.ceil((double) source.length() / 1024 / 1024 / 20));//只用线程池开启5个核心线程做复制,ArrayBlockingQueue<>(10)队列的长度为10ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 100, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new ThreadFactory() {private final AtomicInteger threadCount = new AtomicInteger(1);private final String namePrefix = "MyPool-thread-";@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r, namePrefix + threadCount.getAndIncrement());t.setDaemon(false);t.setPriority(Thread.NORM_PRIORITY);return t;}});for (int i = 0; i < threadNum; i++) {threadPoolExecutor.execute(new MyRunnable(i, source, target));}threadPoolExecutor.shutdown();}
}class MyRunnable implements Runnable {private int num;private File source;private File target;MyRunnable(int num, File source, File target) {this.num = num;this.source = source;this.target = target;}@Overridepublic void run() {try (RandomAccessFile sourceFile = new RandomAccessFile(source, "rw");RandomAccessFile targetFile = new RandomAccessFile(target, "rw");) {System.out.println(Thread.currentThread().getName() + "线程启动");sourceFile.seek(num * 1024 * 1024 * 20);targetFile.seek(num * 1024 * 1024 * 20);byte[] buffer = null;if ((sourceFile.length() - sourceFile.getFilePointer()) < 1024 * 1024 * 20) {buffer = new byte[(int) (sourceFile.length() - sourceFile.getFilePointer())];} else {buffer = new byte[1024 * 1024 * 20];}sourceFile.read(buffer);targetFile.write(buffer);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "线程复制结束");}
}

我的windows操作系统的F:\realMe手机文件备份\照片视频\路径下有一个VID20260217183653.mp4文件,上述代码通过多线程将这个文件复制到D盘根目录(D:\)下的copy.mp4文件中,执行结果如下:
clipboard
image

三、对JDK原生的RandomAccessFile进行扩展——增加缓冲区

  从RandomAccessFile的源码中可以看出,RandomAccessFile类在进行读写操作时,都是直接与底层介质进行数据传递的,即使是读写一个字节的数据,也必须进行一次I/O操作,这样就大大降低了其工作的效率,我们可以通过内置一个数据缓存区来提升读写效率,RandomAccessFile也同样可以这样操作,我们可以完全重构一个属于自己的带缓存的BufferedRandomAccessFile 类,如下所示:

package com.xxx.bio;import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;public class BufferedRandomAccessFile extends RandomAccessFile {private static final int Default_Buffer_Size = 1024 * 8;private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;private static final long BuffMask_ = ~(((long) Default_Buffer_Size) - 1L);//表示缓存区是否有未flush的数据。private boolean hasDatas;//表示是否进行同步操作,将缓存内容flush。private boolean syncNeeded_;//当前操作文件的索引位置(包括在缓存区中)。private long cPos = 0L;//磁盘上操作文件的索引位置(存储介质中)。private long diskPos_ = 0L;private long lo_, hi_ = 0L;private long maxHi_ = (long)Default_Buffer_Size;//是否到了文件结束部分。private boolean isEOF;//内置的一个数组缓存区,默认大小是8k。private byte[] buffer;public BufferedRandomAccessFile(File file, String mode) throws IOException {this(file, mode, Default_Buffer_Size);}public BufferedRandomAccessFile(String name, String mode)throws IOException {this(name, mode, Default_Buffer_Size);}public BufferedRandomAccessFile(File file, String mode, int size)throws IOException {super(file, mode);init(size);}public BufferedRandomAccessFile(String name, String mode, int size)throws FileNotFoundException {super(name, mode);init(size);}//对内置缓存区进行初始化private void init(int size) {if (size < Default_Buffer_Size) {size = Default_Buffer_Size;} else if (size > MAX_BUFFER_SIZE) {size = MAX_BUFFER_SIZE;}buffer = new byte[size];}//将缓存区中的数据同步写出到存储介质中。public void sync() throws IOException {if (syncNeeded_) {//将内置缓存区中的数据写入flush();//将文件通道内未写入磁盘的数据强制写入到磁盘中,传入的参数表示是否将文件元信息写入到磁盘之上。getChannel().force(true);syncNeeded_ = false;}}// close前将缓存区刷新一次防止缓存区中有未写入的数据,然后将缓存区置为null,调用父类的close方法释放资源。public void close() throws IOException {this.flush();this.buffer = null;super.close();}//将缓存区中内容写入存储介质中public void flush() throws IOException {this.flushBuffer();}//将缓存中内容写入存储介质之中private void flushBuffer() throws IOException {if (hasDatas) {if (diskPos_ != lo_)super.seek(lo_);int len = (int) (cPos - lo_);super.write(buffer, 0, len);diskPos_ = cPos;hasDatas = false;}}//向缓存区中填充数据。返回实际填充了多少字节的数据。private int fillBuffer() throws IOException {int nextChar = 0;int nChars = buffer.length;//通过一个循环,向缓存区中填充数据,直至将缓存区填满或者文件读到末尾。while (nChars > 0) {int n = super.read(buffer, nextChar, nChars);if (n < 0)break;nextChar += n;nChars -= n;}if ((nextChar < 0) && (isEOF = (nextChar < buffer.length))) {//将为缓存区中未填充到的部分全用-1初始化。Arrays.fill(buffer, nextChar, buffer.length, (byte) 0xff);}diskPos_ += nextChar;return nextChar;}//跳过指定的字节数public void seek(long pos) throws IOException {if (pos >= hi_ || pos < lo_) {flushBuffer();lo_ = pos & BuffMask_;maxHi_ = lo_ + (long) buffer.length;if (diskPos_ != lo_) {super.seek(lo_);diskPos_ = lo_;}int n = fillBuffer();hi_ = lo_ + (long) n;} else {if (pos < cPos) {flushBuffer();}}cPos = pos;}public long getFilePointer() {return cPos;}public long length() throws IOException {return Math.max(cPos, super.length());}public int read() throws IOException {if (cPos >= hi_) {if (isEOF)return -1;seek(cPos);if (cPos == hi_)return -1;}byte res = buffer[(int) (cPos - lo_)];cPos++;return ((int) res) & 0xFF;}public int read(byte[] b) throws IOException {return read(b, 0, b.length);}public int read(byte[] b, int off, int len) throws IOException {if (cPos >= hi_) {if (isEOF)return -1;seek(cPos);if (cPos == hi_)return -1;}len = Math.min(len, (int) (hi_ - cPos));int buffOff = (int) (cPos - lo_);System.arraycopy(buffer, buffOff, b, off, len);cPos += len;return len;}public void write(int b) throws IOException {if (cPos >= hi_) {if (isEOF && hi_ < maxHi_) {hi_++;} else {seek(cPos);if (cPos == hi_) {hi_++;}}}buffer[(int) (cPos - lo_)] = (byte) b;cPos++;hasDatas = true;syncNeeded_ = true;}public void write(byte[] b) throws IOException {write(b, 0, b.length);}public void write(byte[] b, int off, int len) throws IOException {while (len > 0) {int n = writeAtMost(b, off, len);off += n;len -= n;hasDatas = true;syncNeeded_ = true;}}private int writeAtMost(byte[] b, int off, int len) throws IOException {if (cPos >= hi_) {if (isEOF && hi_ < maxHi_) {hi_ = maxHi_;} else {seek(cPos);if (cPos == hi_) {hi_ = maxHi_;}}}len = Math.min(len, (int) (hi_ - cPos));int buffOff = (int) (cPos - lo_);System.arraycopy(b, off, buffer, buffOff, len);cPos += len;return len;}
}
3.1、自定义的BufferedRandomAccessFile 性能对比
package com.xxx.bio;import java.io.*;public class BufferedRandomAccessFilefTest {public static void main(String[] args) {long startTime;long endTime;File source = new File("F:\\realMe手机文件备份\\照片视频\\VID20260217183653.mp4");File target = new File("D:\\copy.mp4");byte[] buffer = new byte[1024];startTime = System.currentTimeMillis();int len;try (RandomAccessFile sourceFile = new RandomAccessFile(source, "rw");RandomAccessFile targetFile = new RandomAccessFile(target, "rw")) {while ((len = sourceFile.read(buffer)) != -1) {targetFile.write(buffer, 0, len);}endTime = System.currentTimeMillis();System.out.println("RandomAccessFile拷贝耗时" + (endTime - startTime)+ "ms");} catch (Exception e) {}startTime = System.currentTimeMillis();try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target));) {while ((len = bis.read(buffer)) != -1) {bos.write(buffer, 0, len);}endTime = System.currentTimeMillis();System.out.println("BufferedInputStream/BuffedOutputStream拷贝耗时"+ (endTime - startTime) + "ms");} catch (Exception e) {}startTime = System.currentTimeMillis();try (BufferedRandomAccessFile sourceFile = new BufferedRandomAccessFile(source, "rw");BufferedRandomAccessFile targetFile = new BufferedRandomAccessFile(target, "rw")) {while ((len = sourceFile.read(buffer)) != -1) {targetFile.write(buffer, 0, len);}endTime = System.currentTimeMillis();System.out.println("BufferedRandomAccessFile拷贝耗时" + (endTime - startTime)+ "ms");} catch (Exception e) {}}
}

上述代码的执行结果如下(拷贝文件的大小为151,808,193 byte):
clipboard

http://www.jsqmd.com/news/704937/

相关文章:

  • 【2026年AI DevOps分水岭】:Docker AI Toolkit全新Agent编排框架上线,支持AutoGen/MetaGPT原生集成——现在不装,下周CI/CD流水线将自动拒绝旧版镜像
  • 沃尔玛购物卡回收平台TOP榜:2026闲置商超卡安全处理实测 - 鼎鼎收礼品卡回收
  • 从LlamaDeploy到Llama-Agents:智能体工作流生产级部署实战指南
  • SpringBoot 集成 OAuth2.0 资源服务器与授权服务器
  • 解密高效PDF文本提取:3个创新方法提升工作效率
  • 魔兽世界API与宏工具实战指南:一站式开发与游戏优化方案
  • MCP 2026多租户隔离配置全链路解析,从vCPU亲和性到TLS 1.3租户证书绑定,覆盖7层隔离面
  • 2026年4月防静电地板品牌权威排名榜 TOP6(最新数据版) - 小艾信息发布
  • 风控实时特征总拖慢 RT?滑动窗口、实时计数、聚合更新到底该怎么做(可落地版)
  • [C# 开发] FolderIconFix
  • 3大突破:快速掌握XLeRobot强化学习训练实战技巧
  • 如何排查ORA-12514报错_监听程序当前无法识别连接描述符
  • OpenFace完全指南:如何快速掌握面部行为分析技术
  • 06华夏之光永存:电磁弹射+一次性火箭航天入轨方案【第六篇:电磁弹射核心电池组参数与供配电优化方案】
  • VS Code Copilot Next 配置失效?立即诊断你的自动化工作流:4类典型故障码+实时修复CLI工具(v1.3.0限时开源)
  • ncmppGui:终极免费NCM音乐解密工具完整指南
  • LightGBM核心原理与工业级应用实战指南
  • Qwen3.5-2B图文理解效果展示:复杂流程图自动解析与说明生成
  • 5分钟掌握:百度网盘直链解析工具完全手册
  • 携程任我行卡回收平台TOP榜:鼎鼎收2026闲置出行卡安全处理指南 - 鼎鼎收礼品卡回收
  • Phi-4-mini-flash-reasoning多场景:从单题求解到批量PRD分析的扩展路径
  • 网络受限环境下的OOTDiffusion虚拟试衣AI完整部署实战指南
  • AI提效Android开发全景图:从需求到上线的AI工具链
  • 如何彻底解决Windows和Office激活问题:KMS_VL_ALL_AIO完整使用方案
  • CCPC 2024 河南省赛
  • GLM-4V-9B实战体验:上传图片就能问答,小白也能轻松玩
  • Cursor Pro免费激活解决方案:三步解锁AI编程完整功能
  • 机器学习k折交叉验证:k值选择与性能评估指南
  • 告别硬件IIC:STM32F103用软件模拟IIC读写AT24C02/04/16全攻略(含地址计算详解)
  • 高权限AI智能体零信任安全实践:三层防御矩阵与自动化部署指南