RandomAccessFile 是什么?它和普通流有什么区别、能做什么?
简化版
**RandomAccessFile(随机访问文件)是 Java 里一个「能在文件任意位置读写」的类——和普通的流(只能从头到尾顺序读、或从头写)不同,它维护一个「文件指针(file pointer)」,你可以用 seek(position) 把指针移动到文件的任意字节位置,然后从那里读或写。**它既能读又能写(一个对象同时支持),构造时用「模式」指定:"r"(只读)、"rw"(读写)等。能做什么(普通流做不到的):① 随机读写——直接跳到文件第 1000 字节处修改(不用读完前面的);② 断点续传/分块下载——多线程各下载文件的一段(每个线程 seek 到自己负责的偏移量写入);③ 读写文件的特定部分——如只改文件头、读文件中间的某条记录;④ 实现简单的随机存取存储(如自己实现一个基于文件的数据结构)。它还能读写各种基本类型(readInt/writeLong 等,类似 DataInputStream)。核心区别:普通流是「单向、顺序」的水流,RandomAccessFile 是「可随机定位、可读可写」的文件游标。
详细版
RandomAccessFile vs 普通流:
| 维度 | 普通流(FileInputStream 等) | RandomAccessFile |
|---|---|---|
| 访问方式 | 顺序(从头到尾) | 随机(seek 到任意位置) |
| 读写 | 一个流要么读要么写 | 一个对象可读可写(rw 模式) |
| 文件指针 | 隐式、单向前进 | 显式 seek/getFilePointer 控制 |
| 基本类型读写 | 需 DataInputStream 包装 | 自带 readInt/writeLong 等 |
| 典型用途 | 顺序读写、流式处理 | 随机读写、断点续传、记录存取 |
// 打开文件("rw" 读写模式)
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) {
// 移动文件指针到第 100 字节
raf.seek(100);
// 从这里读一个 int
int value = raf.readInt();
// 移动到第 200 字节,写一个 long
raf.seek(200);
raf.writeLong(123456L);
// 拿当前指针位置
long pos = raf.getFilePointer();
// 文件总长度
long len = raf.length();
}
// 断点续传:从上次下载的位置继续写
try (RandomAccessFile raf = new RandomAccessFile("download.tmp", "rw")) {
raf.seek(alreadyDownloaded); // 跳到已下载的位置
raf.write(newData); // 从这里继续写
}
⚠️
RandomAccessFile的核心能力是「随机定位」,这正是「多线程分块下载/断点续传」的实现基础。想象下载一个 1GB 的文件:用普通流只能从头顺序写;而用RandomAccessFile,可以开 4 个线程,线程 1 负责 0256MB、线程 2 负责 256512MB……每个线程seek到自己负责的偏移量、并行写入同一个文件的不同区域——因为能随机定位,各线程互不干扰。断点续传同理:记录「已下载到哪个字节」,下次seek到那个位置继续写,不用重新下载。注意:RandomAccessFile是基于字节的,且不带缓冲(每次 read/write 可能是系统调用),对大量小读写性能不如缓冲流;它擅长的是「需要随机定位」的场景,不是「顺序批量读写」(那用缓冲流更好)。
完整版教学
一、普通流的局限:只能顺序
先理解普通流「顺序」的局限,才理解为什么需要 RandomAccessFile:
普通流(FileInputStream/FileOutputStream):
像"水流"——只能从头到尾单向流动
FileInputStream:从文件开头顺序读到结尾,指针只能前进
FileOutputStream:从头开始写(或追加模式从末尾写)
局限:
① 不能"跳着读":想读第 1000 字节,得先读完前 999 字节(或 skip 跳过)
② 不能"原地改":想改文件中间某处,普通流做不到
(FileOutputStream 打开会截断文件或只能追加)
③ 一个流要么读要么写:读用 InputStream、写用 OutputStream
场景需求(普通流搞不定):
- 只改文件头的几个字节(如改文件的元数据)
- 多线程各写文件的一段(分块下载)
- 读文件中间的某条固定长度记录(随机存取)
→ 需要"能随机定位 + 能读能写"的工具 → RandomAccessFile
普通流的局限是「只能顺序」——像水流单向流动:FileInputStream 从头顺序读、指针只能前进,不能跳着读、不能原地改,一个流要么读要么写。这搞不定「只改文件头、多线程写文件不同段、读中间某条记录」等需求。所以需要「能随机定位 + 能读能写」的 RandomAccessFile。理解「普通流只能顺序(不能跳读/不能原地改/要么读要么写)、搞不定随机定位需求、所以需要 RandomAccessFile」,就理解了它存在的理由。
二、文件指针:随机定位的核心
RandomAccessFile 的核心是「文件指针」——可随意移动的读写位置:
文件指针(file pointer):
一个记录"当前读写位置"的偏移量(从文件开头算的字节数)
所有读写都从当前指针位置开始,读写后指针自动前进
核心方法:
seek(long pos):把指针移动到第 pos 字节(随机定位!)
getFilePointer():拿当前指针位置
length():文件总长度(字节)
skipBytes(int n):指针前进 n 字节
随机读写的本质:
read/write 之前,先 seek 到你想要的位置
→ 就能读写文件的任意位置
例:读第 1000 字节处的一个 int
raf.seek(1000); // 指针跳到第 1000 字节
int x = raf.readInt(); // 从这读,读完指针到 1004
对比普通流:普通流指针只能"顺序前进"(read 一次前进一次)
RandomAccessFile 指针可以"任意跳"(seek)
RandomAccessFile 的核心是「文件指针」——记录当前读写位置的偏移量,所有读写从指针位置开始、读写后指针前进。关键方法:seek(pos) 移动指针到任意位置(随机定位)、getFilePointer() 拿当前位置、length() 文件长度。随机读写的本质是「read/write 前先 seek 到目标位置」。对比普通流指针只能顺序前进,RandomAccessFile 指针可任意跳。理解「文件指针记录读写位置、seek 移动到任意位置(随机定位)、随机读写=read/write 前先 seek、指针可任意跳 vs 普通流只能顺序前进」,就理解了随机定位的核心。
三、读写模式
RandomAccessFile 构造时要指定「模式」:
构造:new RandomAccessFile(file, mode)
mode 是字符串,常用:
"r":只读——只能读,写会抛异常
"rw":读写——能读能写(文件不存在会创建)
"rws":读写 + 同步(每次写内容和元数据都同步到磁盘,慢但安全)
"rwd":读写 + 同步内容(只同步内容,不同步元数据)
一个对象同时能读能写(rw 模式):
这是和普通流的重要区别——
普通流:FileInputStream 只读、FileOutputStream 只写(两个对象)
RandomAccessFile:一个对象既 read 又 write
"rws"/"rwd" 的用途:
普通 "rw" 写可能先在 OS 缓存里,不保证立即落盘
"rws"/"rwd" 保证每次写都同步到磁盘(类似 fsync)
→ 对数据安全要求高(不能丢)时用,但慢
RandomAccessFile 构造要指定模式:"r"(只读)、"rw"(读写,文件不存在会创建)、"rws"(读写+同步内容和元数据到磁盘)、"rwd"(读写+只同步内容)。一个对象同时能读能写(rw)是和普通流的重要区别(普通流读写是两个对象)。"rws"/"rwd" 保证每次写同步到磁盘(类似 fsync,数据安全但慢),用于不能丢数据的场景。理解「模式 r(只读)/rw(读写,不存在会创建)/rws-rwd(读写+同步磁盘)、一个对象能读能写是和普通流区别、rws/rwd 保证落盘但慢」,就掌握了读写模式。
四、应用一:断点续传与分块下载
RandomAccessFile 最经典的应用是「断点续传 / 多线程分块下载」:
多线程分块下载(下载一个大文件):
文件 1GB,开 4 个线程并行下载:
线程1:下载 0 ~ 256MB,seek(0) 后写
线程2:下载 256 ~ 512MB,seek(256MB) 后写
线程3:下载 512 ~ 768MB,seek(512MB) 后写
线程4:下载 768MB ~ 1GB,seek(768MB) 后写
→ 每个线程 seek 到自己负责的偏移,并行写同一文件的不同区域
→ 因为能随机定位,各线程互不干扰,并行加速下载
断点续传(下载中断后继续):
1. 记录"已下载到第几字节"(如 300MB)
2. 下次继续时:raf.seek(300MB) → 从这里往后写
3. 不用重新下载前面 300MB
→ 靠随机定位,从断点处继续
前提:能随机定位(seek)——普通流做不到(只能从头顺序写)
所以下载工具、分块上传都用 RandomAccessFile
RandomAccessFile 最经典的应用是「断点续传 / 多线程分块下载」——下载大文件时开多个线程,每个线程 seek 到自己负责的偏移量、并行写同一文件的不同区域(能随机定位所以互不干扰);断点续传则记录「已下载到第几字节」,下次 seek 到那里继续写、不用重下。这些都靠随机定位(普通流只能从头顺序写做不到)。理解「多线程分块下载:每线程 seek 到自己的偏移并行写不同区域;断点续传:记录已下载位置、seek 到那里继续写;都靠随机定位普通流做不到」,就掌握了这个经典应用。
五、应用二:定长记录的随机存取
另一个应用是「基于文件的随机存取存储(定长记录)」:
场景:把一批固定长度的记录存到文件,要能"按序号快速读某条"
比如每条记录 100 字节,要读第 50 条记录
用 RandomAccessFile:
第 n 条记录的位置 = n × 记录长度
读第 50 条:raf.seek(50 * 100); 然后读 100 字节
→ O(1) 定位到任意一条记录,不用遍历
这是"简易文件数据库"的思路:
定长记录 + 随机定位 = 按索引快速存取
(很多简单的嵌入式存储、索引文件用这个思路)
对比:如果用普通流,读第 50 条要从头读到第 50 条(顺序),慢
RandomAccessFile 还自带基本类型读写:
readInt/writeInt、readLong/writeLong、readUTF/writeUTF...
→ 方便读写结构化的定长记录(不用再包 DataInputStream)
另一个应用是「定长记录的随机存取」——把一批固定长度的记录存文件,要按序号快速读某条:第 n 条记录的位置 = n × 记录长度,seek(n×len) 就能 O(1) 定位到任意记录,不用遍历。这是「简易文件数据库」的思路(定长记录 + 随机定位 = 按索引快速存取)。RandomAccessFile 还自带基本类型读写(readInt/writeLong/readUTF,不用包 DataInputStream),方便读写结构化定长记录。理解「定长记录随机存取:第 n 条位置=n×记录长度、seek 到位置 O(1)读取、简易文件数据库思路、自带基本类型读写方便结构化记录」,就掌握了这个应用。
六、注意事项与选择
RandomAccessFile 有几个注意点,以及和其他方式的选择:
注意事项:
① 不带缓冲:每次 read/write 可能是系统调用
→ 大量小读写性能不如缓冲流
→ 需要时可自己配合 ByteBuffer 或包装
② 基于字节:处理文本要自己管编码(不像 Reader 自动解码)
③ 单线程内一个 RandomAccessFile 的指针是共享的
→ 多线程用同一个对象要小心(各线程 seek 会互相影响)
→ 分块下载通常每个线程用自己的 RandomAccessFile 对象
和其他方式的选择:
顺序读写、流式处理 → 普通流 + 缓冲(BufferedInputStream)
随机定位、断点续传、记录存取 → RandomAccessFile
高性能随机访问大文件 → FileChannel + MappedByteBuffer(内存映射,见相关题)
→ NIO 的 FileChannel 也支持随机访问(position()),且能内存映射,
对超大文件随机访问性能更好
结论:
RandomAccessFile 是"能随机定位的读写文件"的简单方案
需要极致性能/超大文件 → 考虑 FileChannel
RandomAccessFile 的注意点:① 不带缓冲(每次读写可能是系统调用,大量小读写性能不如缓冲流);② 基于字节(处理文本要自己管编码);③ 多线程用同一对象要小心(指针共享,分块下载通常每线程用自己的对象)。选择:顺序读写用普通流+缓冲、随机定位/断点续传用 RandomAccessFile、高性能随机访问超大文件用 FileChannel+MappedByteBuffer(内存映射)。理解「RandomAccessFile 不带缓冲(小读写慢)/基于字节(自己管编码)/多线程指针共享要小心;选择:顺序用缓冲流、随机定位用 RandomAccessFile、超大文件高性能用 FileChannel」,就掌握了注意点和选择。
记忆钩子:「RandomAccessFile=能在文件任意位置读写的类(和普通流只能顺序不同);核心=文件指针,seek(pos)移动到任意位置(随机定位),read/write 前先 seek;一个对象能读能写(rw 模式,和普通流两个对象不同);模式 r/rw/rws-rwd(同步落盘);自带 readInt/writeLong 等基本类型读写;经典应用:①多线程分块下载(每线程 seek 到自己偏移并行写不同区域)②断点续传(记录已下载位置 seek 继续)③定长记录随机存取(第 n 条=n×长度,O(1)定位);注意:不带缓冲(小读写慢)、基于字节、多线程指针共享要小心;超大文件高性能用 FileChannel+MappedByteBuffer」。
七、常见误区与追问
- 误区:RandomAccessFile 只能读或只能写。 它一个对象同时能读能写(“rw” 模式)——这是和普通流的重要区别(普通流 FileInputStream 只读、FileOutputStream 只写,是两个对象);RandomAccessFile 用 seek 定位后既能 read 又能 write。
- 误区:想读文件第 1000 字节要先读完前面的。 用 RandomAccessFile 不用——seek(1000) 直接把文件指针跳到第 1000 字节,然后从那里读;这正是「随机访问」的意义,普通流才需要顺序读或 skip 跳过前面的字节。
- 误区:RandomAccessFile 带缓冲,性能好。 它不带缓冲——每次 read/write 可能是一次系统调用,对大量小读写性能不如缓冲流;它擅长的是「需要随机定位」的场景(断点续传、记录存取),顺序批量读写还是缓冲流更好。
- 误区:多线程分块下载可以共用一个 RandomAccessFile。 一个 RandomAccessFile 的文件指针是共享的,多线程各自 seek 会互相影响指针位置;分块下载通常每个线程创建自己的 RandomAccessFile 对象(各有独立指针),seek 到自己负责的区域写。
- 追问:断点续传是怎么用 RandomAccessFile 实现的? 记录「已下载到第几字节」(如 300MB),下载中断后重新开始时,用 raf.seek(300MB) 把文件指针跳到已下载的位置,从这里继续 write 后续数据,不用重新下载前面的 300MB;靠 seek 的随机定位能力实现「从断点续」。
- 追问:多线程分块下载为什么能用 RandomAccessFile? 因为能随机定位——把大文件分成几段,每个线程负责一段(如线程1管0~256MB),各线程用自己的 RandomAccessFile seek 到负责的偏移量、并行写同一文件的不同区域;随机定位保证各线程写不同位置互不干扰,从而并行加速下载。
- 追问:RandomAccessFile 和 FileChannel 怎么选? RandomAccessFile 是简单的随机读写方案(seek + read/write);FileChannel(NIO)也支持随机访问(position()),且能内存映射(MappedByteBuffer)把文件映射到内存、对超大文件随机访问性能更好、还支持文件锁、transferTo 零拷贝等;需要极致性能或操作超大文件时用 FileChannel,简单随机读写用 RandomAccessFile 就够。
八、加强记忆
RandomAccessFile(随机访问文件)能在文件的任意位置读写——和普通流(只能从头到尾顺序)不同,它维护一个「文件指针」,用 seek(pos) 移动指针到任意字节位置(随机定位),所有 read/write 从指针位置开始。一个对象同时能读能写("rw" 模式,和普通流「读写是两个对象」不同),构造模式:"r"(只读)、"rw"(读写,不存在会创建)、"rws"/"rwd"(读写 + 同步落盘,安全但慢)。它自带基本类型读写(readInt/writeLong/readUTF,不用包 DataInputStream)。经典应用:① 多线程分块下载(每个线程 seek 到自己负责的偏移量、并行写同一文件的不同区域,随机定位保证互不干扰);② 断点续传(记录已下载到第几字节,下次 seek 到那里继续写、不用重下);③ 定长记录随机存取(第 n 条记录位置 = n × 记录长度,seek 到位置 O(1) 读取,简易文件数据库思路)。注意:不带缓冲(大量小读写慢)、基于字节(文本要自己管编码)、多线程用同一对象要小心(指针共享)。选择:顺序读写用缓冲流、随机定位用 RandomAccessFile、超大文件高性能随机访问用 FileChannel+MappedByteBuffer。一句话「RandomAccessFile=能随机定位读写文件(seek 到任意位置)、一个对象能读能写(rw 模式)、自带基本类型读写;经典应用多线程分块下载(每线程 seek 到自己偏移并行写)/断点续传(seek 到已下载位置继续)/定长记录随机存取(n×长度 O(1)定位);不带缓冲小读写慢、超大文件用 FileChannel」。