【Java】随机文件读写利器:RandomAccessFile详解

RandomAccessFile是 Java 输入/输出 (I/O) 体系中一个功能强大且独特的类。它不同于普通的InputStreamOutputStream,它允许程序随机访问文件的内容,即可以在文件的任意位置进行读写操作。

以下是关于RandomAccessFile的详细解析:

1. 核心特点

  • 随机访问:它包含一个文件指针。通过移动这个指针,你可以跳转到文件的任何位置进行读写,而不必像流那样必须从头读到尾。
  • 读写一体:同一个类既支持读也支持写,不需要像其他流那样分开使用FileInputStreamFileOutputStream
  • 支持基本类型:它实现了DataInputDataOutput接口,可以直接读写int,long,double,UTF字符串等基本数据类型。

2. 构造方法与模式

构造方法需要两个参数:文件路径/对象 和 模式。

RandomAccessFileraf=newRandomAccessFile(Filefile,Stringmode);// 或RandomAccessFileraf=newRandomAccessFile(Stringname,Stringmode);

Mode 参数说明:

模式说明
r只读方式打开。调用 write 方法会抛出IOException。如果文件不存在,也会抛出异常。
rw读写方式打开。如果文件不存在,会尝试创建新文件。
rwd读写模式。要求对文件内容的每个更新都同步写入底层存储设备(磁盘)。这能保证数据不丢失,但速度较慢。
rws读写模式。要求对文件内容和元数据(如文件大小、修改时间)的每个更新都同步写入底层存储设备。比rwd更慢。

3. 常用 API 方法

文件指针操作

  • long getFilePointer():获取当前文件指针的位置。
  • void seek(long pos)最重要方法。将文件指针移动到指定位置(从文件开头算起的字节数)。
  • void skipBytes(int n):尝试跳过 n 个字节。
  • long length():获取文件的长度(总字节数)。

读写操作

  • 基本类型readInt(),writeInt(int v),readLong(),writeLong(long v),readDouble(),writeDouble(double v)等。
  • 字节read(),read(byte[] b),write(int b),write(byte[] b)
  • 字符串readUTF(),writeUTF(String str)(写入带长度前缀的 UTF-8 字符串)。也可以配合getBytes()使用普通的字节数组读写。

4. 代码示例

示例一:基本的写入与随机读取

importjava.io.RandomAccessFile;importjava.io.IOException;publicclassRAFDemo{publicstaticvoidmain(String[]args)throwsIOException{StringfilePath="demo.txt";// 1. 写入数据// 模式 "rw": 如果文件不存在会自动创建try(RandomAccessFileraf=newRandomAccessFile(filePath,"rw")){// 写入一个整数 (占用4个字节)raf.writeInt(100);// 写入一个长整数 (占用8个字节)raf.writeLong(123456789L);// 写入字符串raf.writeUTF("Hello Java");System.out.println("写入完成,当前指针位置: "+raf.getFilePointer());// 写入完成,当前指针位置: 24}// 2. 随机读取数据try(RandomAccessFileraf=newRandomAccessFile(filePath,"r")){// 文件总大小System.out.println("文件总大小: "+raf.length()+" bytes");// 文件总大小: 32 bytes// 假设我们只想读取那个 Long 值// Int占4字节,所以我们要跳过前4个字节raf.seek(4);longvalue=raf.readLong();System.out.println("读取到的 Long 值: "+value);// 如果想读取字符串,需要再次移动指针// Int(4) + Long(8) = 12raf.seek(12);Stringstr=raf.readUTF();System.out.println("读取到的字符串: "+str);}}}

示例二:实现断点续传/多线程下载原理

RandomAccessFile最经典的用途就是多线程下载。基本思路是:在本地创建一个与服务器资源大小相同的空文件,然后每个线程负责写入文件的一部分。

// 模拟多线程下载分块写入publicvoiddownloadPart(longstart,longend,byte[]data)throwsIOException{try(RandomAccessFileraf=newRandomAccessFile("video.mp4","rw")){// 移动指针到该线程负责的起始位置raf.seek(start);// 写入数据raf.write(data);}}

5. 注意事项与“坑”

1. 覆盖问题

RandomAccessFile的写入是覆盖式的,而不是插入式的。
如果你把指针移动到文件中间并写入数据,新数据会覆盖掉原有位置的数据,而不会把后面的数据向后挤。

  • 如果你想插入数据:必须先读取插入点之后的所有数据,写入新数据后,再把旧数据写回去。

2. 文件大小

如果使用rw模式,当文件不存在时会自动创建。如果文件存在,RandomAccessFile不会清空文件,而是保留原有内容。如果你需要重置文件,可以先调用raf.setLength(0)

3. 线程安全

RandomAccessFile本身不是线程安全的。如果在多线程环境下使用同一个RandomAccessFile实例,必须自行添加同步机制,或者每个线程维护自己的实例(但要注意底层文件系统的并发限制)。

6. 总结

什么时候使用RandomAccessFile

  • 需要操作大文件:比如视频文件编辑、大型日志文件分析。
  • 实现下载功能:断点续传、多线程下载。
  • 数据库模拟:存储固定格式的记录(定长记录),可以直接计算偏移量跳转读取。
  • 需要频繁在文件中跳转:而不是从头读到尾。

什么时候不要使用?

  • 简单的文本文件读写(使用BufferedReader/BufferedWriter更方便)。
  • 文件内容需要频繁插入修改(效率极低)。
  • 需要高级序列化功能(不如ObjectOutputStream方便)。