【Java】随机文件读写利器:RandomAccessFile详解
RandomAccessFile是 Java 输入/输出 (I/O) 体系中一个功能强大且独特的类。它不同于普通的InputStream和OutputStream,它允许程序随机访问文件的内容,即可以在文件的任意位置进行读写操作。
以下是关于RandomAccessFile的详细解析:
1. 核心特点
- 随机访问:它包含一个文件指针。通过移动这个指针,你可以跳转到文件的任何位置进行读写,而不必像流那样必须从头读到尾。
- 读写一体:同一个类既支持读也支持写,不需要像其他流那样分开使用
FileInputStream和FileOutputStream。 - 支持基本类型:它实现了
DataInput和DataOutput接口,可以直接读写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方便)。
